From dbbdd1dfc3a130be46d4586133daa36c67378e7a Mon Sep 17 00:00:00 2001 From: JR Conlin Date: Fri, 14 Jun 2024 12:51:02 -0700 Subject: [PATCH] feat: Remove support for BrowserID (#1531) * feat: Remove support for BrowserID * mark test only functions as such. I think we can drop MapAndThenTrait for more recent implementations of rust. Closes: SYNC-3684 --- .circleci/config.yml | 2 +- Cargo.lock | 712 +++++++++++------- Cargo.toml | 17 +- syncserver/Cargo.toml | 5 +- syncserver/src/error.rs | 12 +- syncserver/src/server/test.rs | 4 +- syncserver/src/tokenserver/extractors.rs | 104 +-- syncserver/src/tokenserver/mod.rs | 10 +- syncserver/src/web/auth.rs | 8 +- syncserver/src/web/error.rs | 3 +- syncserver/src/web/handlers.rs | 13 +- syncstorage-db/Cargo.toml | 2 +- syncstorage-settings/src/lib.rs | 11 +- tests.ini | 2 +- tokenserver-auth/src/browserid.rs | 643 ---------------- tokenserver-auth/src/crypto.rs | 2 + tokenserver-auth/src/lib.rs | 2 - tokenserver-common/Cargo.toml | 1 + tokenserver-common/src/error.rs | 11 +- tokenserver-settings/src/lib.rs | 18 - tools/hawk/make_hawk_token.py | 2 +- tools/integration_tests/run.py | 12 +- tools/integration_tests/test_support.py | 2 + .../tokenserver/mock_fxa_server.py | 30 - tools/integration_tests/tokenserver/run.py | 14 +- .../tokenserver/test_authorization.py | 96 ++- .../tokenserver/test_browserid.py | 562 -------------- .../integration_tests/tokenserver/test_e2e.py | 93 +-- .../tokenserver/test_support.py | 57 +- tools/tokenserver/loadtests/locustfile.py | 89 --- tools/tokenserver/util.py | 5 +- 31 files changed, 578 insertions(+), 1966 deletions(-) delete mode 100644 tokenserver-auth/src/browserid.rs delete mode 100644 tools/integration_tests/tokenserver/test_browserid.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 29d1110c..e806a101 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -192,7 +192,7 @@ jobs: RUST_BACKTRACE: 1 # XXX: begin_test_transaction doesn't play nice over threaded tests RUST_TEST_THREADS: 1 - - image: circleci/mysql:5.7-ram + - image: cimg/mysql:5.7 auth: username: $DOCKER_USER password: $DOCKER_PASS diff --git a/Cargo.lock b/Cargo.lock index d4a20aae..82f44f10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,9 +21,9 @@ dependencies = [ [[package]] name = "actix-cors" -version = "0.6.5" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0346d8c1f762b41b458ed3145eea914966bb9ad20b9be0d6d463b20d45586370" +checksum = "f9e772b3bcafe335042b5db010ab7c09013dad6eac4915c91d8d50902769f331" dependencies = [ "actix-utils", "actix-web", @@ -45,7 +45,7 @@ dependencies = [ "actix-service", "actix-utils", "ahash", - "base64", + "base64 0.21.7", "bitflags 2.5.0", "brotli", "bytes", @@ -55,7 +55,7 @@ dependencies = [ "flate2", "futures-core", "h2", - "http", + "http 0.2.12", "httparse", "httpdate", "itoa", @@ -80,7 +80,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -90,9 +90,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d22475596539443685426b6bdadb926ad0ecaefdfc5fb05e5e3441f15463c511" dependencies = [ "bytestring", - "http", + "http 0.2.12", "regex", - "serde 1.0.197", + "serde 1.0.200", "tracing", ] @@ -176,7 +176,7 @@ dependencies = [ "once_cell", "pin-project-lite", "regex", - "serde 1.0.197", + "serde 1.0.200", "serde_json", "serde_urlencoded", "smallvec", @@ -194,7 +194,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -274,10 +274,59 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.81" +name = "anstream" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" [[package]] name = "arc-swap" @@ -297,19 +346,19 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" dependencies = [ - "serde 1.0.197", + "serde 1.0.200", "serde_json", ] [[package]] name = "async-trait" -version = "0.1.79" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -325,9 +374,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" @@ -350,6 +399,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bindgen" version = "0.59.2" @@ -453,21 +508,22 @@ dependencies = [ [[package]] name = "cadence" -version = "0.29.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f39286bc075b023101dccdb79456a1334221c768b8faede0c2aff7ed29a9482d" +checksum = "2f338b979d9ebfff4bb9801ae8f3af0dc3615f7f1ca963f2e4782bcf9acb3753" dependencies = [ "crossbeam-channel", ] [[package]] name = "cc" -version = "1.0.92" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -487,16 +543,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", - "num-traits 0.2.18", + "num-traits 0.2.19", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -534,6 +590,12 @@ dependencies = [ "cc", ] +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + [[package]] name = "colored" version = "2.1.0" @@ -552,7 +614,7 @@ checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" dependencies = [ "lazy_static", "nom 5.1.3", - "serde 1.0.197", + "serde 1.0.200", ] [[package]] @@ -564,7 +626,7 @@ dependencies = [ "lazy_static", "nom 5.1.3", "rust-ini", - "serde 1.0.197", + "serde 1.0.200", "serde-hjson", "serde_json", "toml", @@ -588,16 +650,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -695,7 +747,7 @@ dependencies = [ "config 0.10.1", "crossbeam-queue", "num_cpus", - "serde 1.0.197", + "serde 1.0.200", "tokio", ] @@ -705,7 +757,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ - "serde 1.0.197", + "serde 1.0.200", "uuid", ] @@ -821,7 +873,7 @@ checksum = "7f3f119846c823f9eafcf953a8f6ffb6ed69bf6240883261a7f13b634579a51f" dependencies = [ "lazy_static", "regex", - "serde 1.0.197", + "serde 1.0.200", "strsim 0.10.0", ] @@ -833,19 +885,29 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "either" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" version = "0.9.3" @@ -861,15 +923,15 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ + "anstream", + "anstyle", + "env_filter", "humantime", - "is-terminal", "log", - "regex", - "termcolor", ] [[package]] @@ -884,7 +946,7 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" dependencies = [ - "serde 1.0.197", + "serde 1.0.200", ] [[package]] @@ -911,9 +973,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -996,7 +1058,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -1042,9 +1104,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -1118,7 +1180,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.12", "indexmap", "slab", "tokio", @@ -1128,9 +1190,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hawk" @@ -1139,7 +1201,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ba86b7cbed4f24e509c720688eaf4963eac20d9341689bf69bcf5ee5e0f1cd2" dependencies = [ "anyhow", - "base64", + "base64 0.21.7", "log", "once_cell", "ring", @@ -1203,13 +1265,13 @@ dependencies = [ [[package]] name = "hostname" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" dependencies = [ + "cfg-if", "libc", - "match_cfg", - "winapi", + "windows", ] [[package]] @@ -1224,13 +1286,36 @@ dependencies = [ ] [[package]] -name = "http-body" -version = "0.4.6" +name = "http" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", - "http", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.1.0", + "http-body", "pin-project-lite", ] @@ -1254,40 +1339,58 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.28" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", - "h2", - "http", + "http 1.1.0", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", - "socket2", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] name = "hyper-rustls" -version = "0.24.2" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", - "http", + "http 1.1.0", "hyper", + "hyper-util", "rustls", + "rustls-pki-types", "tokio", "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -1383,6 +1486,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itoa" version = "1.0.11" @@ -1391,9 +1500,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] @@ -1413,10 +1522,10 @@ version = "9.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" dependencies = [ - "base64", + "base64 0.21.7", "js-sys", "ring", - "serde 1.0.197", + "serde 1.0.200", "serde_json", ] @@ -1453,9 +1562,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libloading" @@ -1464,7 +1573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -1520,9 +1629,9 @@ checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1534,12 +1643,6 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "matches" version = "0.1.10" @@ -1676,14 +1779,14 @@ version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" dependencies = [ - "num-traits 0.2.18", + "num-traits 0.2.19", ] [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1738,15 +1841,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" dependencies = [ "log", - "serde 1.0.197", + "serde 1.0.200", "windows-sys 0.52.0", ] [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -1754,15 +1857,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -1789,6 +1892,26 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1851,9 +1974,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -1911,7 +2034,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -1924,14 +2047,14 @@ dependencies = [ "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1979,11 +2102,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", ] [[package]] @@ -2028,20 +2151,20 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.11.27" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ - "base64", + "base64 0.22.1", "bytes", - "encoding_rs", "futures-core", "futures-util", - "h2", - "http", + "http 1.1.0", "http-body", + "http-body-util", "hyper", "hyper-rustls", + "hyper-util", "ipnet", "js-sys", "log", @@ -2051,11 +2174,11 @@ dependencies = [ "pin-project-lite", "rustls", "rustls-pemfile", - "serde 1.0.197", + "rustls-pki-types", + "serde 1.0.200", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", "tokio-rustls", "tower-service", @@ -2111,9 +2234,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", @@ -2124,32 +2247,42 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.11" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", "ring", + "rustls-pki-types", "rustls-webpki", - "sct", + "subtle", + "zeroize", ] [[package]] name = "rustls-pemfile" -version = "1.0.4" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64", + "base64 0.22.1", + "rustls-pki-types", ] [[package]] -name = "rustls-webpki" -version = "0.101.7" +name = "rustls-pki-types" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" + +[[package]] +name = "rustls-webpki" +version = "0.102.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" dependencies = [ "ring", + "rustls-pki-types", "untrusted", ] @@ -2198,16 +2331,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "semver" version = "1.0.22" @@ -2216,9 +2339,9 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "sentry" -version = "0.31.8" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce4b57f1b521f674df7a1d200be8ff5d74e3712020ee25b553146657b5377d5" +checksum = "00421ed8fa0c995f07cde48ba6c89e80f2b312f74ff637326f392fbfd23abe02" dependencies = [ "curl", "httpdate", @@ -2231,9 +2354,9 @@ dependencies = [ [[package]] name = "sentry-backtrace" -version = "0.31.8" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cc8d4e04a73de8f718dc703943666d03f25d3e9e4d0fb271ca0b8c76dfa00e" +checksum = "a79194074f34b0cbe5dd33896e5928bbc6ab63a889bd9df2264af5acb186921e" dependencies = [ "backtrace", "once_cell", @@ -2243,9 +2366,9 @@ dependencies = [ [[package]] name = "sentry-contexts" -version = "0.31.8" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6436c1bad22cdeb02179ea8ef116ffc217797c028927def303bc593d9320c0d1" +checksum = "eba8870c5dba2bfd9db25c75574a11429f6b95957b0a78ac02e2970dd7a5249a" dependencies = [ "hostname", "libc", @@ -2257,22 +2380,22 @@ dependencies = [ [[package]] name = "sentry-core" -version = "0.31.8" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901f761681f97db3db836ef9e094acdd8756c40215326c194201941947164ef1" +checksum = "46a75011ea1c0d5c46e9e57df03ce81f5c7f0a9e199086334a1f9c0a541e0826" dependencies = [ "once_cell", "rand", "sentry-types", - "serde 1.0.197", + "serde 1.0.200", "serde_json", ] [[package]] name = "sentry-debug-images" -version = "0.31.8" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afdb263e73d22f39946f6022ed455b7561b22ff5553aca9be3c6a047fa39c328" +checksum = "7ec2a486336559414ab66548da610da5e9626863c3c4ffca07d88f7dc71c8de8" dependencies = [ "findshlibs", "once_cell", @@ -2281,9 +2404,9 @@ dependencies = [ [[package]] name = "sentry-tracing" -version = "0.31.8" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82eabcab0a047040befd44599a1da73d3adb228ff53b5ed9795ae04535577704" +checksum = "f715932bf369a61b7256687c6f0554141b7ce097287e30e3f7ed6e9de82498fe" dependencies = [ "sentry-backtrace", "sentry-core", @@ -2293,14 +2416,14 @@ dependencies = [ [[package]] name = "sentry-types" -version = "0.31.8" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da956cca56e0101998c8688bc65ce1a96f00673a0e58e663664023d4c7911e82" +checksum = "4519c900ce734f7a0eb7aba0869dfb225a7af8820634a7dd51449e3b093cfb7c" dependencies = [ "debugid", "hex", "rand", - "serde 1.0.197", + "serde 1.0.200", "serde_json", "thiserror", "time", @@ -2316,9 +2439,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] @@ -2337,24 +2460,24 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", - "serde 1.0.197", + "serde 1.0.200", ] [[package]] @@ -2366,7 +2489,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.197", + "serde 1.0.200", ] [[package]] @@ -2399,9 +2522,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -2458,7 +2581,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f400f1c5db96f1f52065e8931ca0c524cceb029f7537c9e6d5424488ca137ca0" dependencies = [ "chrono", - "serde 1.0.197", + "serde 1.0.200", "serde_json", "slog", ] @@ -2506,9 +2629,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2557,9 +2680,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.58" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -2582,7 +2705,7 @@ dependencies = [ "actix-web", "async-trait", "backtrace", - "base64", + "base64 0.22.1", "cadence", "chrono", "docopt", @@ -2593,13 +2716,14 @@ dependencies = [ "hex", "hmac", "hostname", + "http 1.1.0", "lazy_static", "mime", "rand", "regex", "sentry", "sentry-backtrace", - "serde 1.0.197", + "serde 1.0.200", "serde_derive", "serde_json", "sha2", @@ -2637,7 +2761,7 @@ dependencies = [ "cadence", "futures 0.3.30", "hkdf", - "serde 1.0.197", + "serde 1.0.200", "serde_json", "sha2", "slog", @@ -2653,7 +2777,7 @@ dependencies = [ "diesel", "diesel_migrations", "futures 0.3.30", - "http", + "http 1.1.0", "syncserver-common", "thiserror", ] @@ -2664,7 +2788,7 @@ version = "0.16.0" dependencies = [ "config 0.11.0", "num_cpus", - "serde 1.0.197", + "serde 1.0.200", "slog-scope", "syncserver-common", "syncstorage-settings", @@ -2678,7 +2802,7 @@ version = "0.16.0" dependencies = [ "async-trait", "cadence", - "env_logger 0.10.2", + "env_logger 0.11.3", "futures 0.3.30", "hostname", "lazy_static", @@ -2705,9 +2829,9 @@ dependencies = [ "diesel", "diesel_migrations", "futures 0.3.30", - "http", + "http 1.1.0", "lazy_static", - "serde 1.0.197", + "serde 1.0.200", "serde_json", "syncserver-common", "syncserver-db-common", @@ -2720,13 +2844,13 @@ version = "0.16.0" dependencies = [ "async-trait", "backtrace", - "base64", + "base64 0.22.1", "diesel", "diesel_logger", "diesel_migrations", - "env_logger 0.10.2", + "env_logger 0.11.3", "futures 0.3.30", - "http", + "http 1.1.0", "slog-scope", "syncserver-common", "syncserver-db-common", @@ -2742,7 +2866,7 @@ name = "syncstorage-settings" version = "0.16.0" dependencies = [ "rand", - "serde 1.0.197", + "serde 1.0.200", "syncserver-common", "time", ] @@ -2755,12 +2879,12 @@ dependencies = [ "backtrace", "cadence", "deadpool", - "env_logger 0.10.2", + "env_logger 0.11.3", "form_urlencoded", "futures 0.3.30", "google-cloud-rust-raw", "grpcio", - "http", + "http 1.1.0", "log", "protobuf", "slog-scope", @@ -2774,27 +2898,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "take_mut" version = "0.2.2" @@ -2838,22 +2941,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -2868,15 +2971,15 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde 1.0.197", + "serde 1.0.200", "time-core", "time-macros", ] @@ -2889,9 +2992,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -2917,7 +3020,7 @@ name = "tokenserver-auth" version = "0.16.0" dependencies = [ "async-trait", - "base64", + "base64 0.22.1", "dyn-clone", "futures 0.3.30", "hex", @@ -2928,7 +3031,7 @@ dependencies = [ "pyo3", "reqwest", "ring", - "serde 1.0.197", + "serde 1.0.200", "serde_json", "sha2", "slog-scope", @@ -2945,8 +3048,9 @@ version = "0.16.0" dependencies = [ "actix-web", "backtrace", + "http 1.1.0", "jsonwebtoken", - "serde 1.0.197", + "serde 1.0.200", "serde_json", "syncserver-common", "thiserror", @@ -2962,10 +3066,10 @@ dependencies = [ "diesel", "diesel_logger", "diesel_migrations", - "env_logger 0.10.2", + "env_logger 0.11.3", "futures 0.3.30", - "http", - "serde 1.0.197", + "http 1.1.0", + "serde 1.0.200", "serde_derive", "serde_json", "slog-scope", @@ -2983,7 +3087,7 @@ name = "tokenserver-settings" version = "0.16.0" dependencies = [ "jsonwebtoken", - "serde 1.0.197", + "serde 1.0.200", "tokenserver-common", ] @@ -3013,31 +3117,31 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] name = "tokio-rustls" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ "rustls", + "rustls-pki-types", "tokio", ] [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -3046,9 +3150,31 @@ version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ - "serde 1.0.197", + "serde 1.0.200", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -3129,9 +3255,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "unindent" @@ -3165,7 +3291,7 @@ dependencies = [ "form_urlencoded", "idna 0.5.0", "percent-encoding 2.3.1", - "serde 1.0.197", + "serde 1.0.200", ] [[package]] @@ -3174,6 +3300,12 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "uuid" version = "1.8.0" @@ -3181,7 +3313,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", - "serde 1.0.197", + "serde 1.0.200", ] [[package]] @@ -3193,7 +3325,7 @@ dependencies = [ "idna 0.4.0", "lazy_static", "regex", - "serde 1.0.197", + "serde 1.0.200", "serde_derive", "serde_json", "url 2.5.0", @@ -3295,7 +3427,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", "wasm-bindgen-shared", ] @@ -3329,7 +3461,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3352,9 +3484,12 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.4" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "which" @@ -3386,11 +3521,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -3399,13 +3534,23 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.5", +] + [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -3423,7 +3568,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -3443,17 +3588,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -3464,9 +3610,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -3476,9 +3622,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -3488,9 +3634,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -3500,9 +3652,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -3512,9 +3664,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -3524,9 +3676,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -3536,15 +3688,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winreg" -version = "0.50.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" dependencies = [ "cfg-if", "windows-sys 0.48.0", @@ -3571,24 +3723,30 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "087eca3c1eaf8c47b94d02790dd086cd594b912d2043d4de4bfdd466b3befb7c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "6f4b6c273f496d8fd4eaf18853e6b448760225dc030ff2c485a786859aea6393" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + [[package]] name = "zstd" version = "0.13.1" diff --git a/Cargo.toml b/Cargo.toml index 67b1eaaf..e166d125 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,12 +30,12 @@ license = "MPL-2.0" [workspace.dependencies] actix-web = "4" -base64 = "0.21" -cadence = "0.29" +base64 = "0.22" +cadence = "1.3" backtrace = "0.3" chrono = "0.4" docopt = "1.1" -env_logger = "0.10" +env_logger = "0.11" futures = { version = "0.3", features = ["compat"] } futures-util = { version = "0.3", features = [ "async-await", @@ -44,30 +44,31 @@ futures-util = { version = "0.3", features = [ "io", ] } hex = "0.4" +hostname = "0.4" hkdf = "0.12" hmac = "0.12" -http = "0.2" +http = "1.1" jsonwebtoken = { version = "9.2", default-features = false } lazy_static = "1.4" protobuf = "=2.25.2" # pin to 2.25.2 to prevent side updating rand = "0.8" regex = "1.4" -reqwest = { version = "0.11", default-features = false, features = [ +reqwest = { version = "0.12", default-features = false, features = [ "rustls-tls", ] } -sentry = { version = "0.31", default-features = false, features = [ +sentry = { version = "0.32", default-features = false, features = [ "curl", "backtrace", "contexts", "debug-images", ] } -sentry-backtrace = "0.31" +sentry-backtrace = "0.32" serde = "1.0" serde_derive = "1.0" serde_json = { version = "1.0", features = ["arbitrary_precision"] } sha2 = "0.10" slog = { version = "2.5", features = [ - "max_level_info", + "max_level_trace", "release_max_level_info", "dynamic-keys", ] } diff --git a/syncserver/Cargo.toml b/syncserver/Cargo.toml index 67583a08..f3a1f5f8 100644 --- a/syncserver/Cargo.toml +++ b/syncserver/Cargo.toml @@ -16,6 +16,8 @@ docopt.workspace = true futures.workspace = true futures-util.workspace = true hex.workspace = true +hostname.workspace = true +http.workspace = true lazy_static.workspace = true rand.workspace = true regex.workspace = true @@ -37,10 +39,9 @@ thiserror.workspace = true actix-http = "3" actix-rt = "2" -actix-cors = "0.6" +actix-cors = "0.7" async-trait = "0.1.40" dyn-clone = "1.0.4" -hostname = "0.3.1" hawk = "5.0" mime = "0.3" # pin to 0.19: https://github.com/getsentry/sentry-rust/issues/277 diff --git a/syncserver/src/error.rs b/syncserver/src/error.rs index f03069ac..86d6a3a5 100644 --- a/syncserver/src/error.rs +++ b/syncserver/src/error.rs @@ -9,10 +9,10 @@ use std::convert::From; use std::fmt; use actix_web::{ - dev::ServiceResponse, error::ResponseError, http::StatusCode, middleware::ErrorHandlerResponse, - HttpResponse, HttpResponseBuilder, Result, + dev::ServiceResponse, error::ResponseError, middleware::ErrorHandlerResponse, HttpResponse, + HttpResponseBuilder, Result, }; - +use http::StatusCode; use serde::{ ser::{SerializeMap, SerializeSeq, Serializer}, Serialize, @@ -117,7 +117,7 @@ impl ApiError { Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) } else { // Replace the outbound error message with our own for Sync requests. - let resp = HttpResponseBuilder::new(StatusCode::NOT_FOUND) + let resp = HttpResponseBuilder::new(actix_web::http::StatusCode::NOT_FOUND) .json(WeaveError::UnknownError as u32); Ok(ErrorHandlerResponse::Response(ServiceResponse::new( res.request().clone(), @@ -194,7 +194,9 @@ impl ResponseError for ApiError { // HttpResponse::build(self.status).json(self) // // So instead we translate our error to a backwards compatible one - let mut resp = HttpResponse::build(self.status); + let mut resp = HttpResponse::build( + actix_web::http::StatusCode::from_u16(self.status.as_u16()).unwrap(), + ); if self.is_conflict() { resp.insert_header(("Retry-After", RETRY_AFTER.to_string())); }; diff --git a/syncserver/src/server/test.rs b/syncserver/src/server/test.rs index 8598e36c..8354089e 100644 --- a/syncserver/src/server/test.rs +++ b/syncserver/src/server/test.rs @@ -6,7 +6,6 @@ use actix_web::{ http::{ self, header::{HeaderName, HeaderValue}, - StatusCode, }, test, web::Bytes, @@ -15,6 +14,7 @@ use base64::{engine, Engine}; use chrono::offset::Utc; use hawk::{self, Credentials, Key, RequestBuilder}; use hmac::{Hmac, Mac}; +use http::StatusCode; use lazy_static::lazy_static; use rand::{thread_rng, Rng}; use serde::de::DeserializeOwned; @@ -172,7 +172,7 @@ fn create_hawk_header(method: &str, port: u16, path: &str) -> String { user_id: 42, fxa_uid: format!("xxx_test_uid_{}", *RAND_UID), fxa_kid: format!("xxx_test_kid_{}", *RAND_UID), - device_id: "xxx_test".to_owned(), + hashed_device_id: "xxx_test".to_owned(), tokenserver_origin: Default::default(), }; let payload = diff --git a/syncserver/src/tokenserver/extractors.rs b/syncserver/src/tokenserver/extractors.rs index 359458f7..adb3fa8a 100644 --- a/syncserver/src/tokenserver/extractors.rs +++ b/syncserver/src/tokenserver/extractors.rs @@ -9,7 +9,6 @@ use std::sync::Arc; use actix_web::{ dev::Payload, - http::StatusCode, web::{Data, Query}, FromRequest, HttpRequest, }; @@ -17,6 +16,7 @@ use base64::{engine, Engine}; use futures::future::LocalBoxFuture; use hex; use hmac::{Hmac, Mac}; +use http::StatusCode; use lazy_static::lazy_static; use regex::Regex; use serde::Deserialize; @@ -98,14 +98,12 @@ impl TokenserverRequest { }); } - // If the client previously reported a client state, every subsequent request must include - // one. Note that this is only relevant for BrowserID requests, since OAuth requests must - // always include a client state. + // If the caller reports new client state, but the auth doesn't, flag + // it as an error. if !self.user.client_state.is_empty() && self.auth_data.client_state.is_empty() { let error_message = "Unacceptable client-state value empty string".to_owned(); return Err(TokenserverError::invalid_client_state(error_message, None)); } - // The client state on the request must not have been used in the past. if self .user @@ -159,14 +157,11 @@ impl TokenserverRequest { }); } - // If there's no keys_changed_at on the request, there must be no value stored on the user - // record. Note that this is only relevant for BrowserID requests, since OAuth requests - // must always include a keys_changed_at header. The Python Tokenserver converts a NULL - // keys_changed_at on the user record to 0 in memory, which means that NULL - // keys_changed_ats are treated equivalently to 0 keys_changed_ats. This would allow users - // with a 0 keys_changed_at on their user record to hold off on sending a keys_changed_at - // in requests even though the value in the database is non-NULL. To be thorough, we - // handle this case here. + // Oauth requests must always include a `keys_changed_at` header. The Python Tokenserver + // converts a NULL `keys_changed_at` to 0 in memory, which means that NULL `keys_changed_at`s + // are treated equivalenty to 0 `keys_changed_at`s. This would allow users with a 0 `keys_changed_at` + // on their user record to hold off on sending a `keys_changed_at` in requests even though the + // value in the database is non-NULL. To be thorough, we handle this case here. if auth_keys_changed_at.is_none() && matches!(user_keys_changed_at, Some(inner) if inner != 0) { @@ -178,7 +173,6 @@ impl TokenserverRequest { ..TokenserverError::invalid_keys_changed_at() }); } - Ok(()) } } @@ -209,12 +203,8 @@ impl FromRequest for TokenserverRequest { log_items_mutator.insert("metrics_uid".to_owned(), hashed_fxa_uid.clone()); // To preserve anonymity, compute a hash of the FxA device ID to be used for reporting - // metrics. Only requests using BrowserID will have a device ID, so use "none" as - // a placeholder for OAuth requests. - let hashed_device_id = { - let device_id = auth_data.device_id.as_deref().unwrap_or("none"); - hash_device_id(&hashed_fxa_uid, device_id, fxa_metrics_hash_secret) - }; + // metrics. Use "none" as a placeholder for "device" with OAuth requests. + let hashed_device_id = hash_device_id(&hashed_fxa_uid, fxa_metrics_hash_secret); let DbWrapper(db) = DbWrapper::extract(&req).await?; let service_id = { @@ -352,10 +342,9 @@ impl FromRequest for DbPoolWrapper { } } -/// An authentication token as parsed from the `Authorization` header. Both BrowserID assertions -/// and OAuth tokens are opaque to Tokenserver and must be verified via FxA. +/// An authentication token as parsed from the `Authorization` header. +/// OAuth tokens are opaque to Tokenserver and must be verified via FxA. pub enum Token { - BrowserIdAssertion(String), OAuthToken(String), } @@ -393,10 +382,8 @@ impl FromRequest for Token { if auth_type == "bearer" { Ok(Token::OAuthToken(token.to_owned())) - } else if auth_type == "browserid" { - Ok(Token::BrowserIdAssertion(token.to_owned())) } else { - // The request must use a Bearer token or BrowserID token + // The request must use a Bearer token Err(TokenserverError { description: "Unsupported".to_owned(), location: ErrorLocation::Body, @@ -421,7 +408,6 @@ impl FromRequest for Token { #[derive(Debug, Default, Eq, PartialEq)] pub struct AuthData { pub client_state: String, - pub device_id: Option, pub email: String, pub fxa_uid: String, pub generation: Option, @@ -452,37 +438,6 @@ impl FromRequest for AuthData { } match token { - Token::BrowserIdAssertion(assertion) => { - // Add a tag to the request extensions - req.add_tag("token_type".to_owned(), "BrowserID".to_owned()); - log_items_mutator.insert("token_type".to_owned(), "BrowserID".to_owned()); - - // Start a timer with the same tag - let mut tags = HashMap::default(); - tags.insert("token_type".to_owned(), "BrowserID".to_owned()); - metrics.start_timer("token_verification", Some(tags)); - let verify_output = - state.browserid_verifier.verify(assertion, &metrics).await?; - - // For requests using BrowserID, the client state is embedded in the - // X-Client-State header, and the generation and keys_changed_at are extracted - // from the assertion as part of the verification process. - let XClientStateHeader(client_state) = - XClientStateHeader::extract(&req).await?; - let (fxa_uid, _) = verify_output - .email - .split_once('@') - .unwrap_or((&verify_output.email, "")); - - Ok(AuthData { - client_state: client_state.unwrap_or_else(|| "".to_owned()), - device_id: verify_output.device_id, - email: verify_output.email.clone(), - fxa_uid: fxa_uid.to_owned(), - generation: convert_zero_to_none(verify_output.generation), - keys_changed_at: convert_zero_to_none(verify_output.keys_changed_at), - }) - } Token::OAuthToken(token) => { // Add a tag to the request extensions req.add_tag("token_type".to_owned(), "OAuth".to_owned()); @@ -503,7 +458,6 @@ impl FromRequest for AuthData { Ok(AuthData { client_state: key_id.client_state, email, - device_id: None, fxa_uid, generation: convert_zero_to_none(verify_output.generation), keys_changed_at: convert_zero_to_none(Some(key_id.keys_changed_at)), @@ -686,9 +640,14 @@ fn fxa_metrics_hash(fxa_uid: &str, hmac_key: &[u8]) -> String { hex::encode(result) } -fn hash_device_id(fxa_uid: &str, device: &str, hmac_key: &[u8]) -> String { +fn hash_device_id(fxa_uid: &str, hmac_key: &[u8]) -> String { let mut to_hash = String::from(fxa_uid); - to_hash.push_str(device); + // TODO: This value originally was the deviceID from BrowserID. + // When support was dropped for BrowserID, the device string + // defaulted to "none". Append it here for now as a hard coded + // value until we can figure out if it's something we need to + // preserve for the UA or not. + to_hash.push_str("none"); let fxa_metrics_hash = fxa_metrics_hash(&to_hash, hmac_key); String::from(&fxa_metrics_hash[0..32]) @@ -709,7 +668,7 @@ mod tests { use serde_json; use syncserver_settings::Settings as GlobalSettings; use syncstorage_settings::ServerLimits; - use tokenserver_auth::{browserid, oauth, MockVerifier}; + use tokenserver_auth::{oauth, MockVerifier}; use tokenserver_db::mock::MockDbPool as MockTokenserverPool; use tokenserver_settings::Settings as TokenserverSettings; @@ -740,7 +699,7 @@ mod tests { verify_output, } }; - let state = make_state(oauth_verifier, MockVerifier::default()); + let state = make_state(oauth_verifier); let req = TestRequest::default() .data(state) @@ -761,7 +720,6 @@ mod tests { let expected_tokenserver_request = TokenserverRequest { user: results::GetOrCreateUser::default(), auth_data: AuthData { - device_id: None, fxa_uid: fxa_uid.to_owned(), email: "test123@test.com".to_owned(), generation: Some(1234), @@ -794,7 +752,7 @@ mod tests { verify_output, } }; - let state = make_state(oauth_verifier, MockVerifier::default()); + let state = make_state(oauth_verifier); let request = TestRequest::default() .data(state) @@ -837,7 +795,7 @@ mod tests { }; TestRequest::default() - .data(make_state(oauth_verifier, MockVerifier::default())) + .data(make_state(oauth_verifier)) .data(Arc::clone(&SECRETS)) .insert_header(("authorization", "Bearer fake_token")) .insert_header(("accept", "application/json,text/plain:q=0.5")) @@ -942,7 +900,7 @@ mod tests { }; TestRequest::default() - .data(make_state(oauth_verifier, MockVerifier::default())) + .data(make_state(oauth_verifier)) .insert_header(("authorization", "Bearer fake_token")) .insert_header(("accept", "application/json,text/plain:q=0.5")) .param("application", "sync") @@ -1095,7 +1053,6 @@ mod tests { old_client_states: vec![], }, auth_data: AuthData { - device_id: None, fxa_uid: "test".to_owned(), email: "test@test.com".to_owned(), generation: Some(1233), @@ -1138,7 +1095,6 @@ mod tests { old_client_states: vec![], }, auth_data: AuthData { - device_id: None, fxa_uid: "test".to_owned(), email: "test@test.com".to_owned(), generation: Some(1234), @@ -1180,7 +1136,6 @@ mod tests { old_client_states: vec![], }, auth_data: AuthData { - device_id: None, fxa_uid: "test".to_owned(), email: "test@test.com".to_owned(), generation: Some(1234), @@ -1223,7 +1178,6 @@ mod tests { old_client_states: vec!["bbbb".to_owned()], }, auth_data: AuthData { - device_id: None, fxa_uid: "test".to_owned(), email: "test@test.com".to_owned(), generation: Some(1234), @@ -1266,7 +1220,6 @@ mod tests { old_client_states: vec![], }, auth_data: AuthData { - device_id: None, fxa_uid: "test".to_owned(), email: "test@test.com".to_owned(), generation: Some(1234), @@ -1307,7 +1260,6 @@ mod tests { old_client_states: vec![], }, auth_data: AuthData { - device_id: None, fxa_uid: "test".to_owned(), email: "test@test.com".to_owned(), generation: Some(1235), @@ -1335,17 +1287,13 @@ mod tests { String::from_utf8(block_on(test::read_body(sresponse)).to_vec()).unwrap() } - fn make_state( - oauth_verifier: MockVerifier, - browserid_verifier: MockVerifier, - ) -> ServerState { + fn make_state(oauth_verifier: MockVerifier) -> ServerState { let syncserver_settings = GlobalSettings::default(); let tokenserver_settings = TokenserverSettings::default(); ServerState { fxa_email_domain: "test.com".to_owned(), fxa_metrics_hash_secret: "".to_owned(), - browserid_verifier: Box::new(browserid_verifier), oauth_verifier: Box::new(oauth_verifier), db_pool: Box::new(MockTokenserverPool::new()), node_capacity_release_rate: None, diff --git a/syncserver/src/tokenserver/mod.rs b/syncserver/src/tokenserver/mod.rs index d7d5dc55..972af18f 100644 --- a/syncserver/src/tokenserver/mod.rs +++ b/syncserver/src/tokenserver/mod.rs @@ -11,7 +11,7 @@ use serde::{ use syncserver_common::{BlockingThreadpool, Metrics}; #[cfg(not(feature = "py_verifier"))] use tokenserver_auth::JWTVerifierImpl; -use tokenserver_auth::{browserid, oauth, VerifyToken}; +use tokenserver_auth::{oauth, VerifyToken}; use tokenserver_common::NodeType; use tokenserver_db::{params, DbPool, TokenserverPool}; use tokenserver_settings::Settings; @@ -21,7 +21,7 @@ use crate::{ server::user_agent, }; -use std::{collections::HashMap, convert::TryFrom, fmt, sync::Arc}; +use std::{collections::HashMap, fmt, sync::Arc}; #[derive(Clone)] pub struct ServerState { @@ -29,7 +29,6 @@ pub struct ServerState { pub fxa_email_domain: String, pub fxa_metrics_hash_secret: String, pub oauth_verifier: Box>, - pub browserid_verifier: Box>, pub node_capacity_release_rate: Option, pub node_type: NodeType, pub metrics: Arc, @@ -72,10 +71,6 @@ impl ServerState { oauth::Verifier::new(settings, blocking_threadpool.clone()) .expect("failed to create Tokenserver OAuth verifier"), ); - let browserid_verifier = Box::new( - browserid::Verifier::try_from(settings) - .expect("failed to create Tokenserver BrowserID verifier"), - ); let use_test_transactions = false; TokenserverPool::new( @@ -102,7 +97,6 @@ impl ServerState { fxa_email_domain: settings.fxa_email_domain.clone(), fxa_metrics_hash_secret: settings.fxa_metrics_hash_secret.clone(), oauth_verifier, - browserid_verifier, db_pool: Box::new(db_pool), node_capacity_release_rate: settings.node_capacity_release_rate, node_type: settings.node_type, diff --git a/syncserver/src/web/auth.rs b/syncserver/src/web/auth.rs index 13fccf38..7b67af1c 100644 --- a/syncserver/src/web/auth.rs +++ b/syncserver/src/web/auth.rs @@ -53,8 +53,8 @@ pub struct HawkPayload { #[serde(default)] pub fxa_kid: String, - #[serde(default, rename = "hashed_device_id")] - pub device_id: String, + #[serde(default)] + pub hashed_device_id: String, /// The Tokenserver that created this token. #[serde(default)] @@ -156,7 +156,7 @@ impl HawkPayload { user_id, fxa_uid: "xxx_test".to_owned(), fxa_kid: "xxx_test".to_owned(), - device_id: "xxx_test".to_owned(), + hashed_device_id: "xxx_test".to_owned(), tokenserver_origin: Default::default(), } } @@ -508,7 +508,7 @@ mod tests { user_id: 1, fxa_uid: "319b98f9961ff1dbdd07313cd6ba925a".to_owned(), fxa_kid: "de697ad66d845b2873c9d7e13b8971af".to_owned(), - device_id: "2bcb92f4d4698c3d7b083a3c698a16ccd78bc2a8d20a96e4bb128ddceaf4e0b6".to_owned(), + hashed_device_id: "2bcb92f4d4698c3d7b083a3c698a16ccd78bc2a8d20a96e4bb128ddceaf4e0b6".to_owned(), tokenserver_origin: Default::default(), }, } diff --git a/syncserver/src/web/error.rs b/syncserver/src/web/error.rs index 44722dd6..cddd343c 100644 --- a/syncserver/src/web/error.rs +++ b/syncserver/src/web/error.rs @@ -2,12 +2,13 @@ #![allow(clippy::single_match)] use std::fmt; -use actix_web::http::{header::ToStrError, StatusCode}; +use actix_web::http::header::ToStrError; use actix_web::Error as ActixError; use base64::DecodeError; use hawk::Error as ParseError; use hmac::digest::{InvalidLength, MacError}; +use http::StatusCode; use serde::{ ser::{SerializeSeq, Serializer}, Serialize, diff --git a/syncserver/src/web/handlers.rs b/syncserver/src/web/handlers.rs index 7c2edc72..c7d4ebf0 100644 --- a/syncserver/src/web/handlers.rs +++ b/syncserver/src/web/handlers.rs @@ -1,6 +1,7 @@ //! API Handlers use std::collections::HashMap; use std::convert::Into; +use std::time::{Duration, Instant}; use actix_web::{http::StatusCode, web::Data, HttpRequest, HttpResponse, HttpResponseBuilder}; use serde::Serialize; @@ -11,7 +12,6 @@ use syncstorage_db::{ results::{CreateBatch, Paginated}, Db, DbError, DbErrorIntrospect, }; -use time; use crate::{ error::{ApiError, ApiErrorKind}, @@ -585,7 +585,7 @@ pub async fn lbheartbeat(req: HttpRequest) -> Result { let deadarc = state.deadman.clone(); let mut deadman = *deadarc.read().await; - if matches!(deadman.expiry, Some(expiry) if expiry <= time::Instant::now()) { + if matches!(deadman.expiry, Some(expiry) if expiry <= Instant::now()) { // We're set to report a failed health check after a certain time (to // evict this instance and start a fresh one) return Ok(HttpResponseBuilder::new(StatusCode::INTERNAL_SERVER_ERROR).json(resp)); @@ -625,7 +625,7 @@ pub async fn lbheartbeat(req: HttpRequest) -> Result { if active >= deadman.max_size && db_state.idle_connections == 0 { if deadman.clock_start.is_none() { - deadman.clock_start = Some(time::Instant::now()); + deadman.clock_start = Some(Instant::now()); } status_code = StatusCode::INTERNAL_SERVER_ERROR; } else if deadman.clock_start.is_some() { @@ -641,11 +641,8 @@ pub async fn lbheartbeat(req: HttpRequest) -> Result { Value::from(db_state.idle_connections), ); if let Some(clock) = deadman.clock_start { - let duration: time::Duration = time::Instant::now() - clock; - resp.insert( - "duration_ms".to_string(), - Value::from(duration.whole_milliseconds()), - ); + let duration: Duration = Instant::now() - clock; + resp.insert("duration_ms".to_string(), Value::from(duration.as_millis())); }; Ok(HttpResponseBuilder::new(status_code).json(json!(resp))) diff --git a/syncstorage-db/Cargo.toml b/syncstorage-db/Cargo.toml index 13d251e5..eb7fdd41 100644 --- a/syncstorage-db/Cargo.toml +++ b/syncstorage-db/Cargo.toml @@ -9,12 +9,12 @@ edition.workspace = true cadence.workspace = true env_logger.workspace = true futures.workspace = true +hostname.workspace = true lazy_static.workspace = true rand.workspace = true slog-scope.workspace = true async-trait = "0.1.40" -hostname = "0.3.1" log = { version = "0.4", features = [ "max_level_debug", "release_max_level_info", diff --git a/syncstorage-settings/src/lib.rs b/syncstorage-settings/src/lib.rs index 4ba335a2..1d3b43a5 100644 --- a/syncstorage-settings/src/lib.rs +++ b/syncstorage-settings/src/lib.rs @@ -1,6 +1,9 @@ //! Application settings objects and initialization -use std::cmp::min; +use std::{ + cmp::min, + time::{Duration, Instant}, +}; use rand::{thread_rng, Rng}; use serde::{Deserialize, Serialize}; @@ -40,8 +43,8 @@ pub struct Quota { pub struct Deadman { pub max_size: u32, pub previous_count: usize, - pub clock_start: Option, - pub expiry: Option, + pub clock_start: Option, + pub expiry: Option, } impl From<&Settings> for Deadman { @@ -52,7 +55,7 @@ impl From<&Settings> for Deadman { let ttl = lbheartbeat_ttl as f32; let max_jitter = ttl * (settings.lbheartbeat_ttl_jitter as f32 * 0.01); let ttl = thread_rng().gen_range(ttl..ttl + max_jitter); - time::Instant::now() + time::Duration::seconds(ttl as i64) + Instant::now() + Duration::from_secs(ttl as u64) }); Deadman { max_size: settings.database_pool_max_size, diff --git a/tests.ini b/tests.ini index 80ebf170..1f55f826 100644 --- a/tests.ini +++ b/tests.ini @@ -20,4 +20,4 @@ batch_upload_enabled = true force_consistent_sort_order = true [hawkauth] -secret = "TED KOPPEL IS A ROBOT" +secret = "secret0" diff --git a/tokenserver-auth/src/browserid.rs b/tokenserver-auth/src/browserid.rs deleted file mode 100644 index 9b3711c7..00000000 --- a/tokenserver-auth/src/browserid.rs +++ /dev/null @@ -1,643 +0,0 @@ -use async_trait::async_trait; -use reqwest::{Client as ReqwestClient, StatusCode}; -use serde::{de::Deserializer, Deserialize, Serialize}; -use syncserver_common::Metrics; -use tokenserver_common::{ErrorLocation, TokenType, TokenserverError}; -use tokenserver_settings::Settings; - -use super::VerifyToken; - -use std::{convert::TryFrom, time::Duration}; - -/// The information extracted from a valid BrowserID assertion. -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)] -pub struct VerifyOutput { - pub device_id: Option, - pub email: String, - pub generation: Option, - pub keys_changed_at: Option, -} - -/// The verifier used to verify BrowserID assertions. -#[derive(Clone)] -pub struct Verifier { - audience: String, - issuer: String, - fxa_verifier_url: String, - // reqwest's async client uses an `Arc` internally, so we don't need to use one here to take - // advantage of keep-alive connections across threads. - request_client: ReqwestClient, -} - -impl TryFrom<&Settings> for Verifier { - type Error = &'static str; - - fn try_from(settings: &Settings) -> Result { - Ok(Self { - audience: settings.fxa_browserid_audience.clone(), - issuer: settings.fxa_browserid_issuer.clone(), - request_client: ReqwestClient::builder() - .timeout(Duration::from_secs(settings.fxa_browserid_request_timeout)) - .connect_timeout(Duration::from_secs(settings.fxa_browserid_connect_timeout)) - .use_rustls_tls() - .build() - .map_err(|_| "failed to build BrowserID reqwest client")?, - fxa_verifier_url: settings.fxa_browserid_server_url.clone(), - }) - } -} - -#[async_trait] -impl VerifyToken for Verifier { - type Output = VerifyOutput; - - /// Verifies a BrowserID assertion. Returns `VerifyOutput` for valid assertions and a - /// `TokenserverError` for invalid assertions. - async fn verify( - &self, - assertion: String, - _metrics: &Metrics, - ) -> Result { - let response = self - .request_client - .post(&self.fxa_verifier_url) - .json(&VerifyRequest { - assertion, - audience: self.audience.clone(), - trusted_issuers: [self.issuer.clone()], - }) - .send() - .await - .map_err(|e| { - if e.is_connect() { - // If we are unable to reach the FxA server or if FxA responds with an HTTP - // status other than 200, report a 503 to the client - TokenserverError { - context: format!( - "Request error occurred during BrowserID request to FxA: {}", - e - ), - token_type: TokenType::BrowserId, - ..TokenserverError::resource_unavailable() - } - } else { - // If any other error occurs during the request, report a 401 to the client - TokenserverError { - context: format!( - "Unknown error occurred during BrowserID request to FxA: {}", - e - ), - token_type: TokenType::BrowserId, - ..TokenserverError::invalid_credentials("Unauthorized".to_owned()) - } - } - })?; - - if response.status() != StatusCode::OK { - return Err(TokenserverError { - context: format!( - "FxA returned a status code other than 200 ({})", - response.status().as_u16() - ), - token_type: TokenType::BrowserId, - ..TokenserverError::resource_unavailable() - }); - } - - // If FxA responds with an invalid response body, report a 503 to the client - let response_body = - response - .json::() - .await - .map_err(|e| TokenserverError { - context: format!( - "Invalid BrowserID verification response received from FxA: {}", - e - ), - token_type: TokenType::BrowserId, - ..TokenserverError::resource_unavailable() - })?; - - match response_body { - VerifyResponse::Failure { - reason: Some(reason), - } if reason.contains("expired") || reason.contains("issued later than") => { - Err(TokenserverError { - status: "invalid-timestamp", - location: ErrorLocation::Body, - context: "Expired BrowserID assertion".to_owned(), - token_type: TokenType::BrowserId, - ..Default::default() - }) - } - VerifyResponse::Failure { - reason: Some(reason), - } => Err(TokenserverError { - context: format!("BrowserID verification error: {}", reason), - token_type: TokenType::BrowserId, - ..TokenserverError::invalid_credentials("Unauthorized".to_owned()) - }), - VerifyResponse::Failure { .. } => Err(TokenserverError { - context: "Unknown BrowserID verification error".to_owned(), - token_type: TokenType::BrowserId, - ..TokenserverError::invalid_credentials("Unauthorized".to_owned()) - }), - VerifyResponse::Okay { issuer, .. } if issuer != self.issuer => Err(TokenserverError { - context: "BrowserID issuer mismatch".to_owned(), - token_type: TokenType::BrowserId, - ..TokenserverError::invalid_credentials("Unauthorized".to_owned()) - }), - VerifyResponse::Okay { - idp_claims: Some(claims), - .. - } if !claims.token_verified() => Err(TokenserverError { - context: "BrowserID assertion not verified".to_owned(), - token_type: TokenType::BrowserId, - ..TokenserverError::invalid_credentials("Unauthorized".to_owned()) - }), - VerifyResponse::Okay { - email, - idp_claims: Some(claims), - .. - } => Ok(VerifyOutput { - device_id: claims.device_id.clone(), - email, - generation: claims.generation()?, - keys_changed_at: claims.keys_changed_at()?, - }), - VerifyResponse::Okay { email, .. } => Ok(VerifyOutput { - device_id: None, - email, - generation: None, - keys_changed_at: None, - }), - } - } -} - -/// The request sent to the FxA BrowserID verifier for token verification. -#[derive(Serialize)] -struct VerifyRequest { - assertion: String, - audience: String, - #[serde(rename(serialize = "trustedIssuers"))] - trusted_issuers: [String; 1], -} - -/// The response returned by the FxA BrowserID verifier for a token verification request. -#[derive(Deserialize, Serialize)] -#[serde(tag = "status", rename_all = "lowercase")] -enum VerifyResponse { - Okay { - email: String, - #[serde(rename = "idpClaims")] - idp_claims: Option, - issuer: String, - }, - Failure { - reason: Option, - }, -} - -/// The claims extracted from a valid BrowserID assertion. -#[derive(Deserialize, Serialize)] -struct IdpClaims { - #[serde(rename = "fxa-deviceId")] - pub device_id: Option, - /// The nested `Option`s are necessary to distinguish between a `null` value and a missing key - /// altogether: `Some(None)` translates to a `null` value and `None` translates to a missing - /// key. - #[serde( - default, - rename = "fxa-generation", - deserialize_with = "strict_deserialize" - )] - generation: Option>, - /// The nested `Option`s are necessary to distinguish between a `null` value and a missing key - /// altogether: `Some(None)` translates to a `null` value and `None` translates to a missing - /// key. - #[serde( - default, - rename = "fxa-keysChangedAt", - deserialize_with = "strict_deserialize" - )] - keys_changed_at: Option>, - /// The nested `Option`s are necessary to distinguish between a `null` value and a missing key - /// altogether: `Some(None)` translates to a `null` value and `None` translates to a missing - /// key. - #[serde( - default, - rename = "fxa-tokenVerified", - deserialize_with = "strict_deserialize" - )] - token_verified: Option>, -} - -impl IdpClaims { - fn generation(&self) -> Result, TokenserverError> { - match self.generation { - // If the fxa-generation claim is present, return its value. If it's missing, return None. - Some(Some(_)) | None => Ok(self.generation.flatten()), - // If the fxa-generation claim is null, return an error. - Some(None) => Err(TokenserverError { - context: "null fxa-generation claim in BrowserID assertion".to_owned(), - token_type: TokenType::BrowserId, - ..TokenserverError::invalid_generation() - }), - } - } - - fn keys_changed_at(&self) -> Result, TokenserverError> { - match self.keys_changed_at { - // If the fxa-keysChangedAt claim is present, return its value. If it's missing, return None. - Some(Some(_)) | None => Ok(self.keys_changed_at.flatten()), - // If the fxa-keysChangedAt claim is null, return an error. - Some(None) => Err(TokenserverError { - description: "invalid keysChangedAt".to_owned(), - status: "invalid-credentials", - location: ErrorLocation::Body, - context: "null fxa-keysChangedAt claim in BrowserID assertion".to_owned(), - token_type: TokenType::BrowserId, - ..Default::default() - }), - } - } - - fn token_verified(&self) -> bool { - match self.token_verified { - // If the fxa-tokenVerified claim is true or missing, return true. - Some(Some(true)) | None => true, - // If the fxa-tokenVerified claim is false or null, return false. - Some(Some(false)) | Some(None) => false, - } - } -} - -// Approach inspired by: https://github.com/serde-rs/serde/issues/984#issuecomment-314143738 -/// This function is used to deserialize JSON fields that may or may not be present. If the field -/// is present, its value is enclosed in `Some`. This results in types of the form -/// `Option>`. If the outer `Option` is `None`, the field wasn't present in the JSON, and -/// if the inner `Option` is `None`, the field was present with a `null` value. -fn strict_deserialize<'de, T, D>(deserializer: D) -> Result, D::Error> -where - T: Deserialize<'de>, - D: Deserializer<'de>, -{ - Deserialize::deserialize(deserializer).map(Some) -} - -#[cfg(test)] -mod tests { - use super::*; - - use mockito::{self, Mock}; - use serde_json::json; - - #[tokio::test] - async fn test_browserid_verifier_success() { - let body = json!({ - "status": "okay", - "email": "test@example.com", - "audience": "https://test.com", - "issuer": "accounts.firefox.com", - "idpClaims": { - "fxa-deviceId": "test_device_id", - "fxa-generation": 1234, - "fxa-keysChangedAt": 5678 - } - }); - let mock = mockito::mock("POST", "/v2") - .with_header("content-type", "application/json") - .with_body(body.to_string()) - .create(); - let verifier = Verifier::try_from(&Settings { - fxa_browserid_audience: "https://test.com".to_owned(), - fxa_browserid_issuer: "accounts.firefox.com".to_owned(), - fxa_browserid_server_url: format!("{}/v2", mockito::server_url()), - ..Default::default() - }) - .unwrap(); - - let result = verifier - .verify("test".to_owned(), &Default::default()) - .await - .unwrap(); - mock.assert(); - - let expected_result = VerifyOutput { - device_id: Some("test_device_id".to_owned()), - email: "test@example.com".to_owned(), - generation: Some(1234), - keys_changed_at: Some(5678), - }; - - assert_eq!(expected_result, result); - } - - #[tokio::test] - async fn test_browserid_verifier_failure_cases() { - const AUDIENCE: &str = "https://test.com"; - - let verifier = Verifier::try_from(&Settings { - fxa_browserid_audience: AUDIENCE.to_owned(), - fxa_browserid_server_url: format!("{}/v2", mockito::server_url()), - ..Default::default() - }) - .unwrap(); - let assertion = "test"; - - // Verifier returns 500 - { - let mock = mockito::mock("POST", "/v2") - .with_status(500) - .with_header("content-type", "application/json") - .create(); - - let error = verifier - .verify(assertion.to_owned(), &Default::default()) - .await - .unwrap_err(); - mock.assert(); - - let expected_error = TokenserverError { - context: "FxA returned a status code other than 200 (500)".to_owned(), - token_type: TokenType::BrowserId, - ..TokenserverError::resource_unavailable() - }; - assert_eq!(expected_error, error); - } - - // "Server Error" in body - { - let mock = mockito::mock("POST", "/v2") - .with_header("content-type", "application/json") - .with_body("

Server Error

") - .create(); - - let error = verifier - .verify(assertion.to_owned(), &Default::default()) - .await - .unwrap_err(); - mock.assert(); - - let expected_error = TokenserverError { - context: "Invalid BrowserID verification response received from FxA: error decoding response body: expected value at line 1 column 1".to_owned(), - token_type: TokenType::BrowserId, - ..TokenserverError::resource_unavailable() - }; - assert_eq!(expected_error, error); - } - - // {"status": "error"} - { - let mock = mockito::mock("POST", "/v2") - .with_header("content-type", "application/json") - .with_body("{\"status\": \"error\"}") - .create(); - - let error = verifier - .verify(assertion.to_owned(), &Default::default()) - .await - .unwrap_err(); - mock.assert(); - - let expected_error = TokenserverError { - context: "Invalid BrowserID verification response received from FxA: error decoding response body: unknown variant `error`, expected `okay` or `failure` at line 1 column 18".to_owned(), - token_type: TokenType::BrowserId, - ..TokenserverError::resource_unavailable() - }; - assert_eq!(expected_error, error); - } - - // {"status": "potato"} in body - { - let mock = mockito::mock("POST", "/v2") - .with_header("content-type", "application/json") - .with_body("{\"status\": \"potato\"}") - .create(); - - let error = verifier - .verify(assertion.to_owned(), &Default::default()) - .await - .unwrap_err(); - mock.assert(); - - let expected_error = TokenserverError { - context: "Invalid BrowserID verification response received from FxA: error decoding response body: unknown variant `potato`, expected `okay` or `failure` at line 1 column 19".to_owned(), - token_type: TokenType::BrowserId, - ..TokenserverError::resource_unavailable() - }; - assert_eq!(expected_error, error); - } - - // {"status": "failure"} in body with random reason - { - let mock = mockito::mock("POST", "/v2") - .with_header("content-type", "application/json") - .with_body("{\"status\": \"failure\", \"reason\": \"something broke\"}") - .create(); - - let error = verifier - .verify(assertion.to_owned(), &Default::default()) - .await - .unwrap_err(); - mock.assert(); - - let expected_error = TokenserverError { - context: "BrowserID verification error: something broke".to_owned(), - token_type: TokenType::BrowserId, - ..TokenserverError::invalid_credentials("Unauthorized".to_owned()) - }; - assert_eq!(expected_error, error); - } - // {"status": "failure"} in body with no reason - { - let mock = mockito::mock("POST", "/v2") - .with_header("content-type", "application/json") - .with_body("{\"status\": \"failure\"}") - .create(); - - let error = verifier - .verify(assertion.to_owned(), &Default::default()) - .await - .unwrap_err(); - mock.assert(); - - let expected_error = TokenserverError { - context: "Unknown BrowserID verification error".to_owned(), - token_type: TokenType::BrowserId, - ..TokenserverError::invalid_credentials("Unauthorized".to_owned()) - }; - assert_eq!(expected_error, error); - } - } - - #[tokio::test] - async fn test_browserid_verifier_rejects_unissuers() { - const AUDIENCE: &str = "https://test.com"; - const ISSUER: &str = "accounts.firefox.com"; - - fn mock(issuer: &'static str) -> Mock { - let body = json!({ - "status": "okay", - "email": "test@example.com", - "audience": "https://testmytoken.com", - "issuer": issuer - }); - - mockito::mock("POST", "/v2") - .with_header("content-type", "application/json") - .with_body(body.to_string()) - .create() - } - - let expected_error = TokenserverError { - context: "BrowserID issuer mismatch".to_owned(), - token_type: TokenType::BrowserId, - ..TokenserverError::invalid_credentials("Unauthorized".to_owned()) - }; - let verifier = Verifier::try_from(&Settings { - fxa_browserid_audience: AUDIENCE.to_owned(), - fxa_browserid_issuer: ISSUER.to_owned(), - fxa_browserid_server_url: format!("{}/v2", mockito::server_url()), - ..Default::default() - }) - .unwrap(); - let assertion = "test".to_owned(); - - { - let mock = mock("login.persona.org"); - let error = verifier - .verify(assertion.clone(), &Default::default()) - .await - .unwrap_err(); - - mock.assert(); - assert_eq!(expected_error, error); - } - - { - let mock = mock(ISSUER); - let result = verifier - .verify(assertion.clone(), &Default::default()) - .await - .unwrap(); - let expected_result = VerifyOutput { - device_id: None, - email: "test@example.com".to_owned(), - generation: None, - keys_changed_at: None, - }; - - mock.assert(); - assert_eq!(expected_result, result); - } - - { - let mock = mock("accounts.firefox.org"); - let error = verifier - .verify(assertion.clone(), &Default::default()) - .await - .unwrap_err(); - - mock.assert(); - assert_eq!(expected_error, error); - } - - { - let mock = mock("http://accounts.firefox.com"); - let error = verifier - .verify(assertion.clone(), &Default::default()) - .await - .unwrap_err(); - - mock.assert(); - assert_eq!(expected_error, error); - } - - { - let mock = mock("accounts.firefox.co"); - let error = verifier - .verify(assertion.clone(), &Default::default()) - .await - .unwrap_err(); - - mock.assert(); - assert_eq!(expected_error, error); - } - - { - let body = json!({ - "status": "okay", - "email": "test@example.com", - "audience": "https://testmytoken.com", - "issuer": 42, - }); - let mock = mockito::mock("POST", "/v2") - .with_header("content-type", "application/json") - .with_body(body.to_string()) - .create(); - let error = verifier - .verify(assertion.clone(), &Default::default()) - .await - .unwrap_err(); - mock.assert(); - - let expected_error = TokenserverError { - context: "Invalid BrowserID verification response received from FxA: error decoding response body: invalid type: integer `42`, expected a string".to_owned(), - token_type: TokenType::BrowserId, - ..TokenserverError::resource_unavailable() - }; - assert_eq!(expected_error, error); - } - - { - let body = json!({ - "status": "okay", - "email": "test@example.com", - "audience": "https://testmytoken.com", - "issuer": None::<()>, - }); - let mock = mockito::mock("POST", "/v2") - .with_header("content-type", "application/json") - .with_body(body.to_string()) - .create(); - let error = verifier - .verify(assertion.clone(), &Default::default()) - .await - .unwrap_err(); - mock.assert(); - - let expected_error = TokenserverError { - context: "Invalid BrowserID verification response received from FxA: error decoding response body: invalid type: null, expected a string".to_owned(), - token_type: TokenType::BrowserId, - ..TokenserverError::resource_unavailable() - }; - assert_eq!(expected_error, error); - } - - { - let body = json!({ - "status": "okay", - "email": "test@example.com", - "audience": "https://testmytoken.com", - }); - let mock = mockito::mock("POST", "/v2") - .with_header("content-type", "application/json") - .with_body(body.to_string()) - .create(); - let error = verifier - .verify(assertion, &Default::default()) - .await - .unwrap_err(); - mock.assert(); - - let expected_error = TokenserverError { - context: "Invalid BrowserID verification response received from FxA: error decoding response body: missing field `issuer`".to_owned(), - token_type: TokenType::BrowserId, - ..TokenserverError::resource_unavailable() - }; - assert_eq!(expected_error, error); - } - } -} diff --git a/tokenserver-auth/src/crypto.rs b/tokenserver-auth/src/crypto.rs index cda8c4a8..fa6c5402 100644 --- a/tokenserver-auth/src/crypto.rs +++ b/tokenserver-auth/src/crypto.rs @@ -24,6 +24,7 @@ pub trait Crypto { #[allow(dead_code)] /// Verify an HMAC signature on a payload given a shared key + #[test] fn hmac_verify(&self, key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), Self::Error>; /// Generates random bytes using a cryptographic random number generator @@ -53,6 +54,7 @@ impl Crypto for CryptoImpl { Ok(mac.finalize().into_bytes().to_vec()) } + #[test] fn hmac_verify(&self, key: &[u8], payload: &[u8], signature: &[u8]) -> Result<(), Self::Error> { let mut mac: Hmac = Hmac::new_from_slice(key).map_err(|_| TokenserverError::internal_error())?; diff --git a/tokenserver-auth/src/lib.rs b/tokenserver-auth/src/lib.rs index f020cd31..636eb751 100644 --- a/tokenserver-auth/src/lib.rs +++ b/tokenserver-auth/src/lib.rs @@ -1,5 +1,3 @@ -pub mod browserid; - #[cfg(not(feature = "py"))] mod crypto; diff --git a/tokenserver-common/Cargo.toml b/tokenserver-common/Cargo.toml index 1ec1f6af..8f4e372e 100644 --- a/tokenserver-common/Cargo.toml +++ b/tokenserver-common/Cargo.toml @@ -8,6 +8,7 @@ edition.workspace = true [dependencies] actix-web.workspace = true backtrace.workspace = true +http.workspace = true serde.workspace = true serde_json.workspace = true jsonwebtoken.workspace = true diff --git a/tokenserver-common/src/error.rs b/tokenserver-common/src/error.rs index f533d2b2..1f4fd94a 100644 --- a/tokenserver-common/src/error.rs +++ b/tokenserver-common/src/error.rs @@ -1,7 +1,8 @@ use std::{cmp::PartialEq, error::Error, fmt}; -use actix_web::{http::StatusCode, HttpResponse, ResponseError}; +use actix_web::{HttpResponse, ResponseError}; use backtrace::Backtrace; +use http::StatusCode; use serde::{ ser::{SerializeMap, Serializer}, Serialize, @@ -27,7 +28,6 @@ pub struct TokenserverError { #[derive(Clone, Debug)] pub enum TokenType { - BrowserId, Oauth, } @@ -201,11 +201,11 @@ impl fmt::Display for ErrorLocation { impl ResponseError for TokenserverError { fn error_response(&self) -> HttpResponse { - HttpResponse::build(self.http_status).json(ErrorResponse::from(self)) + HttpResponse::build(self.status_code()).json(ErrorResponse::from(self)) } - fn status_code(&self) -> StatusCode { - self.http_status + fn status_code(&self) -> actix_web::http::StatusCode { + actix_web::http::StatusCode::from_u16(self.http_status.as_u16()).unwrap() } } @@ -285,7 +285,6 @@ impl ReportableError for TokenserverError { fn metric_label(&self) -> Option { if self.http_status.is_client_error() { match self.token_type { - TokenType::BrowserId => Some("request.error.browser_id".to_owned()), TokenType::Oauth => Some("request.error.oauth".to_owned()), } } else if matches!( diff --git a/tokenserver-settings/src/lib.rs b/tokenserver-settings/src/lib.rs index 8eb611de..273557c2 100644 --- a/tokenserver-settings/src/lib.rs +++ b/tokenserver-settings/src/lib.rs @@ -38,19 +38,6 @@ pub struct Settings { /// A secondary JWK to be used to verify OAuth tokens. This is intended to be used to enable /// seamless key rotations on FxA. pub fxa_oauth_secondary_jwk: Option, - /// The issuer expected in the BrowserID verification response. - pub fxa_browserid_issuer: String, - /// The audience to be sent to the FxA BrowserID verification server. - pub fxa_browserid_audience: String, - /// The URL of the FxA server used for verifying BrowserID assertions. - pub fxa_browserid_server_url: String, - /// The timeout to be used when making requests to the FxA BrowserID verification server. This - /// timeout applies to the duration of the entire request lifecycle, from when the client - /// begins connecting to when the response body has been received. - pub fxa_browserid_request_timeout: u64, - /// The timeout to be used when connecting to the FxA BrowserID verification server. This - /// timeout applies only to the connect portion of the request lifecycle. - pub fxa_browserid_connect_timeout: u64, /// The rate at which capacity should be released from nodes that are at capacity. pub node_capacity_release_rate: Option, /// The type of the storage nodes used by this instance of Tokenserver. @@ -88,11 +75,6 @@ impl Default for Settings { fxa_oauth_request_timeout: 10, fxa_oauth_primary_jwk: None, fxa_oauth_secondary_jwk: None, - fxa_browserid_audience: "https://token.stage.mozaws.net".to_owned(), - fxa_browserid_issuer: "api-accounts.stage.mozaws.net".to_owned(), - fxa_browserid_server_url: "https://verifier.stage.mozaws.net/v2".to_owned(), - fxa_browserid_request_timeout: 10, - fxa_browserid_connect_timeout: 5, node_capacity_release_rate: None, node_type: NodeType::Spanner, statsd_label: "syncstorage.tokenserver".to_owned(), diff --git a/tools/hawk/make_hawk_token.py b/tools/hawk/make_hawk_token.py index ec0c5f50..e5fbfd48 100644 --- a/tools/hawk/make_hawk_token.py +++ b/tools/hawk/make_hawk_token.py @@ -30,7 +30,7 @@ FXA_UID = "DEADBEEF00004be4ae957006c0ceb620" FXA_KID = "DEADBEEF00004be4ae957006c0ceb620" DEVICE_ID = "device1" NODE = "http://localhost:8000" -SECRET = "Ted_Koppel_is_a_robot" +SECRET = os.envrion.get("SYNC_MASTER_SECRET", "Ted_Koppel_is_a_robot") HMAC_KEY = b"foo" # 10 years diff --git a/tools/integration_tests/run.py b/tools/integration_tests/run.py index a8369de0..923faf70 100644 --- a/tools/integration_tests/run.py +++ b/tools/integration_tests/run.py @@ -8,7 +8,7 @@ import sys from test_storage import TestStorage from test_support import run_live_functional_tests import time -from tokenserver.run import run_end_to_end_tests, run_local_tests +from tokenserver.run import (run_end_to_end_tests, run_local_tests) DEBUG_BUILD = "target/debug/syncserver" RELEASE_BUILD = "/app/bin/syncserver" @@ -52,21 +52,16 @@ if __name__ == "__main__": os.environ.setdefault("SYNC_CORS_ALLOWED_ORIGIN", "*") mock_fxa_server_url = os.environ["MOCK_FXA_SERVER_URL"] url = "%s/v2" % mock_fxa_server_url - os.environ["SYNC_TOKENSERVER__FXA_BROWSERID_SERVER_URL"] = url os.environ["SYNC_TOKENSERVER__FXA_OAUTH_SERVER_URL"] = mock_fxa_server_url the_server_subprocess = start_server() try: res = 0 res |= run_live_functional_tests(TestStorage, sys.argv) os.environ["TOKENSERVER_AUTH_METHOD"] = "oauth" - res |= run_local_tests(include_browserid_specific_tests=False) - os.environ["TOKENSERVER_AUTH_METHOD"] = "browserid" - res |= run_local_tests(include_browserid_specific_tests=True) + res |= run_local_tests() finally: terminate_process(the_server_subprocess) - os.environ["SYNC_TOKENSERVER__FXA_BROWSERID_SERVER_URL"] = \ - "https://verifier.stage.mozaws.net/v2" os.environ["SYNC_TOKENSERVER__FXA_OAUTH_SERVER_URL"] = \ "https://oauth.stage.mozaws.net" the_server_subprocess = start_server() @@ -86,7 +81,8 @@ if __name__ == "__main__": the_server_subprocess = start_server() try: - res |= run_end_to_end_tests() + verbosity = int(os.environ.get("VERBOSITY", "1")) + res |= run_end_to_end_tests(verbosity=verbosity) finally: terminate_process(the_server_subprocess) diff --git a/tools/integration_tests/test_support.py b/tools/integration_tests/test_support.py index 9a9d91e7..d8f45cf3 100644 --- a/tools/integration_tests/test_support.py +++ b/tools/integration_tests/test_support.py @@ -794,10 +794,12 @@ class SyncStorageAuthenticationPolicy(TokenServerAuthenticationPolicy): user["hashed_device_id"] = data["hashed_device_id"] if not VALID_FXA_ID_REGEX.match(user["hashed_device_id"]): raise ValueError("invalid hashed_device_id in token data") + """ elif "device_id" in data: user["hashed_device_id"] = data.get("device_id") if not VALID_FXA_ID_REGEX.match(user["hashed_device_id"]): raise ValueError("invalid device_id in token data") + """ return user diff --git a/tools/integration_tests/tokenserver/mock_fxa_server.py b/tools/integration_tests/tokenserver/mock_fxa_server.py index ac3c9935..ddcd5628 100644 --- a/tools/integration_tests/tokenserver/mock_fxa_server.py +++ b/tools/integration_tests/tokenserver/mock_fxa_server.py @@ -22,27 +22,6 @@ def _mock_oauth_jwk(request): return {'keys': [{'fake': 'RSA key'}]} -@view_config(route_name='mock_verify', renderer='json') -def _mock_browserid_verify(request): - body = json.loads(request.json_body['assertion']) - - return Response(json=body['body'], content_type='application/json', - status=body['status']) - - -# This endpoint is used by the legacy Tokenserver during startup. We mock it -# here so the unit tests can be run against the legacy Tokenserver. -@view_config(route_name='mock_config', renderer='json') -def _mock_config(request): - return { - "browserid": { - "issuer": "api-accounts.stage.mozaws.net", - "verificationUrl": "https://verifier.stage.mozaws.net/v2" - }, - "contentUrl": "https://accounts.stage.mozaws.net" - } - - def make_server(host, port): with Configurator() as config: config.add_route('mock_oauth_verify', '/v1/verify') @@ -52,15 +31,6 @@ def make_server(host, port): config.add_route('mock_oauth_jwk', '/v1/jwks') config.add_view(_mock_oauth_jwk, route_name='mock_oauth_jwk', renderer='json') - - config.add_route('mock_browserid_verify', '/v2') - config.add_view(_mock_browserid_verify, - route_name='mock_browserid_verify', - renderer='json') - - config.add_route('mock_config', '/config') - config.add_view(_mock_config, route_name='mock_config', - renderer='json') app = config.make_wsgi_app() return _make_server(host, port, app) diff --git a/tools/integration_tests/tokenserver/run.py b/tools/integration_tests/tokenserver/run.py index 522a18e8..1799c976 100644 --- a/tools/integration_tests/tokenserver/run.py +++ b/tools/integration_tests/tokenserver/run.py @@ -4,32 +4,28 @@ import unittest from tokenserver.test_authorization import TestAuthorization -from tokenserver.test_browserid import TestBrowserId from tokenserver.test_e2e import TestE2e from tokenserver.test_misc import TestMisc from tokenserver.test_node_assignment import TestNodeAssignment -def run_local_tests(include_browserid_specific_tests=True): +def run_local_tests(): test_classes = [TestAuthorization, TestMisc, TestNodeAssignment] - if include_browserid_specific_tests: - test_classes.append(TestBrowserId) - return run_tests(test_classes) -def run_end_to_end_tests(): - return run_tests([TestE2e]) +def run_end_to_end_tests(verbosity=1): + return run_tests([TestE2e], verbosity=verbosity) -def run_tests(test_cases): +def run_tests(test_cases, verbosity=1): loader = unittest.TestLoader() success = True for test_case in test_cases: suite = loader.loadTestsFromTestCase(test_case) - runner = unittest.TextTestRunner() + runner = unittest.TextTestRunner(verbosity=verbosity) res = runner.run(suite) success = success and res.wasSuccessful() diff --git a/tools/integration_tests/tokenserver/test_authorization.py b/tools/integration_tests/tokenserver/test_authorization.py index 1b32a3e9..4cf1370f 100644 --- a/tools/integration_tests/tokenserver/test_authorization.py +++ b/tools/integration_tests/tokenserver/test_authorization.py @@ -45,26 +45,26 @@ class TestAuthorization(TestCase, unittest.TestCase): self.assertEqual(res.json, expected_error_response) def test_invalid_client_state_in_key_id(self): - if self.auth_method == "oauth": - additional_headers = { - 'X-KeyID': "1234-state!" - } - headers = self._build_auth_headers(keys_changed_at=1234, - client_state='aaaa', - **additional_headers) - res = self.app.get('/1.0/sync/1.5', headers=headers, status=401) + additional_headers = { + 'X-KeyID': "1234-state!" + } + headers = self._build_auth_headers( + keys_changed_at=1234, + client_state='aaaa', + **additional_headers) + res = self.app.get('/1.0/sync/1.5', headers=headers, status=401) - expected_error_response = { - 'status': 'invalid-credentials', - 'errors': [ - { - 'location': 'body', - 'name': '', - 'description': 'Unauthorized' - } - ] - } - self.assertEqual(res.json, expected_error_response) + expected_error_response = { + 'status': 'invalid-credentials', + 'errors': [ + { + 'location': 'body', + 'name': '', + 'description': 'Unauthorized' + } + ] + } + self.assertEqual(res.json, expected_error_response) def test_invalid_client_state_in_x_client_state(self): additional_headers = {'X-Client-State': 'state!'} @@ -304,18 +304,6 @@ class TestAuthorization(TestCase, unittest.TestCase): uid = self._add_user(generation=0, keys_changed_at=None, client_state='aaaa') - # Only BrowserID requests can omit keys_changed_at - if self.auth_method == 'browserid': - # Send a request without a generation that doesn't update - # keys_changed_at - headers = self._build_auth_headers(generation=None, - keys_changed_at=None, - client_state='aaaa') - self.app.get('/1.0/sync/1.5', headers=headers) - user = self._get_user(uid) - # This should not have set the user's generation - self.assertEqual(user['generation'], 0) - # Send a request without a generation that updates keys_changed_at headers = self._build_auth_headers(generation=None, keys_changed_at=1234, @@ -534,29 +522,29 @@ class TestAuthorization(TestCase, unittest.TestCase): self.assertEqual(user['keys_changed_at'], 1234) def test_x_client_state_must_have_same_client_state_as_key_id(self): - if self.auth_method == "oauth": - self._add_user(client_state='aaaa') - additional_headers = {'X-Client-State': 'bbbb'} - headers = self._build_auth_headers(generation=1234, - keys_changed_at=1234, - client_state='aaaa', - **additional_headers) - # If present, the X-Client-State header must have the same client - # state as the X-KeyID header - res = self.app.get('/1.0/sync/1.5', headers=headers, status=401) - expected_error_response = { - 'errors': [ - { - 'description': 'Unauthorized', - 'location': 'body', - 'name': '' - } - ], - 'status': 'invalid-client-state' - } - self.assertEqual(res.json, expected_error_response) - headers['X-Client-State'] = 'aaaa' - res = self.app.get('/1.0/sync/1.5', headers=headers) + self._add_user(client_state='aaaa') + additional_headers = {'X-Client-State': 'bbbb'} + headers = self._build_auth_headers( + generation=1234, + keys_changed_at=1234, + client_state='aaaa', + **additional_headers) + # If present, the X-Client-State header must have the same client + # state as the X-KeyID header + res = self.app.get('/1.0/sync/1.5', headers=headers, status=401) + expected_error_response = { + 'errors': [ + { + 'description': 'Unauthorized', + 'location': 'body', + 'name': '' + } + ], + 'status': 'invalid-client-state' + } + self.assertEqual(res.json, expected_error_response) + headers['X-Client-State'] = 'aaaa' + res = self.app.get('/1.0/sync/1.5', headers=headers) def test_zero_generation_treated_as_null(self): # Add a user that has a generation set diff --git a/tools/integration_tests/tokenserver/test_browserid.py b/tools/integration_tests/tokenserver/test_browserid.py deleted file mode 100644 index 80921840..00000000 --- a/tools/integration_tests/tokenserver/test_browserid.py +++ /dev/null @@ -1,562 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this file, -# You can obtain one at http://mozilla.org/MPL/2.0/. -import json -import unittest -from tokenserver.test_support import TestCase - - -class TestBrowserId(TestCase, unittest.TestCase): - def setUp(self): - super(TestBrowserId, self).setUp() - - def tearDown(self): - super(TestBrowserId, self).tearDown() - - def _build_browserid_fxa_error_response(self, reason, status=200): - body = { - 'body': { - 'status': 'failure' - }, - 'status': status - } - - if reason: - body['body']['reason'] = reason - - return { - 'Authorization': 'BrowserID %s' % json.dumps(body), - 'X-Client-State': 'aaaa' - } - - def test_fxa_returns_status_not_ok(self): - expected_error_response = { - 'status': 'error', - 'errors': [ - { - 'location': 'body', - 'description': 'Resource is not available', - 'name': '' - } - ] - } - # If FxA returns any status code other than 200, the client gets a 503 - headers = self._build_browserid_headers(client_state='aaaa', - status=500) - res = self.app.get('/1.0/sync/1.5', headers=headers, status=503) - self.assertEqual(res.json, expected_error_response) - - headers = self._build_browserid_headers(client_state='aaaa', - status=404) - res = self.app.get('/1.0/sync/1.5', headers=headers, status=503) - self.assertEqual(res.json, expected_error_response) - - headers = self._build_browserid_headers(client_state='aaaa', - status=401) - res = self.app.get('/1.0/sync/1.5', headers=headers, status=503) - self.assertEqual(res.json, expected_error_response) - - headers = self._build_browserid_headers(client_state='aaaa', - status=201) - res = self.app.get('/1.0/sync/1.5', headers=headers, status=503) - self.assertEqual(res.json, expected_error_response) - - def test_fxa_returns_invalid_response(self): - # Craft a response that contains invalid JSON - token = json.dumps({ - 'body': {'test': True}, - 'status': 200, - }).replace('true', '') - headers = { - 'Authorization': 'BrowserID %s' % token, - 'X-Client-State': 'aaaa' - } - expected_error_response = { - 'status': 'error', - 'errors': [ - { - 'location': 'body', - 'description': 'Resource is not available', - 'name': '' - } - ] - } - res = self.app.get('/1.0/sync/1.5', headers=headers, status=503) - self.assertEqual(res.json, expected_error_response) - - def test_expired_token(self): - expected_error_response = { - 'status': 'invalid-timestamp', - 'errors': [ - { - 'location': 'body', - 'description': 'Unauthorized', - 'name': '' - } - ] - } - # If the FxA response includes "expired" in the reason message, - # the client gets a 401 and a message indicating an invalid timestamp - headers = self._build_browserid_fxa_error_response('assertion expired') - res = self.app.get('/1.0/sync/1.5', headers=headers, status=401) - self.assertEqual(res.json, expected_error_response) - # If the FxA response includes "issued later than" in the reason - # message, the client gets a 401 and a message indicating an invalid - # timestamp - headers = self._build_browserid_fxa_error_response('issued later than') - res = self.app.get('/1.0/sync/1.5', headers=headers, status=401) - self.assertEqual(res.json, expected_error_response) - - def test_other_reason_message(self): - expected_error_response = { - 'status': 'invalid-credentials', - 'errors': [ - { - 'location': 'body', - 'description': 'Unauthorized', - 'name': '' - } - ] - } - # If the FxA response includes a reason that doesn't indicate an - # invalid timestamp, a generic error is returned - headers = self._build_browserid_fxa_error_response('invalid') - res = self.app.get('/1.0/sync/1.5', headers=headers, status=401) - self.assertEqual(res.json, expected_error_response) - - def test_missing_reason_message(self): - expected_error_response = { - 'status': 'invalid-credentials', - 'errors': [ - { - 'location': 'body', - 'description': 'Unauthorized', - 'name': '' - } - ] - } - # If the FxA response includes no reason, a generic error is returned - headers = self._build_browserid_fxa_error_response(None) - res = self.app.get('/1.0/sync/1.5', headers=headers, status=401) - self.assertEqual(res.json, expected_error_response) - - def test_issuer_mismatch(self): - expected_error_response = { - 'status': 'invalid-credentials', - 'errors': [ - { - 'location': 'body', - 'description': 'Unauthorized', - 'name': '' - } - ] - } - # If the issuer in the response doesn't match the issuer on - # Tokenserver, a 401 is returned - invalid_issuer = 'invalid.com' - headers = self._build_browserid_headers(client_state='aaaa', - issuer=invalid_issuer) - res = self.app.get('/1.0/sync/1.5', headers=headers, status=401) - self.assertEqual(res.json, expected_error_response) - - def test_fxa_error_response_not_ok(self): - expected_error_response = { - 'status': 'error', - 'errors': [ - { - 'location': 'body', - 'description': 'Resource is not available', - 'name': '' - } - ] - } - # If an FxA error response returns a status other than 200, the client - # gets a 503 error - headers = self._build_browserid_fxa_error_response('bad token', - status=401) - res = self.app.get('/1.0/sync/1.5', headers=headers, status=503) - self.assertEqual(res.json, expected_error_response) - - def test_no_idp_claims(self): - # A response from FxA that does not include idpClaims is still valid - headers = self._build_browserid_headers(client_state='aaaa') - self.app.get('/1.0/sync/1.5', headers=headers, status=200) - - def test_partial_idp_claims(self): - # A response from FxA that includes a partially-filled idpClaims - # object is still valid - headers = self._build_browserid_headers(user='test1', - client_state='aaaa', - generation=1234) - self.app.get('/1.0/sync/1.5', headers=headers, status=200) - - headers = self._build_browserid_headers(user='test2', - client_state='aaaa', - keys_changed_at=1234) - self.app.get('/1.0/sync/1.5', headers=headers, status=200) - - headers = self._build_browserid_headers(user='test3', - client_state='aaaa', - device_id='id') - self.app.get('/1.0/sync/1.5', headers=headers, status=200) - - def test_unverified_token(self): - headers = self._build_browserid_headers(client_state='aaaa', - token_verified=None) - # Assertion should not be rejected if fxa-tokenVerified is unset - self.app.get("/1.0/sync/1.5", headers=headers, status=200) - # Assertion should not be rejected if fxa-tokenVerified is true - headers = self._build_browserid_headers(client_state='aaaa', - token_verified=True) - self.app.get("/1.0/sync/1.5", headers=headers, status=200) - # Assertion should be rejected if fxa-tokenVerified is false - headers = self._build_browserid_headers(client_state='aaaa', - token_verified=False) - res = self.app.get("/1.0/sync/1.5", headers=headers, status=401) - expected_error_response = { - 'errors': [ - { - 'description': 'Unauthorized', - 'location': 'body', - 'name': '' - } - ], - 'status': 'invalid-credentials' - } - self.assertEqual(res.json, expected_error_response) - # Assertion should be rejected if fxa-tokenVerified is null - headers['Authorization'] = headers['Authorization'].replace('false', - 'null') - res = self.app.get("/1.0/sync/1.5", headers=headers, status=401) - self.assertEqual(res.json, expected_error_response) - - def test_credentials_from_oauth_and_browserid(self): - # Send initial credentials via oauth. - oauth_headers = self._build_oauth_headers(generation=1234, - keys_changed_at=1234, - client_state='aaaa') - res1 = self.app.get("/1.0/sync/1.5", headers=oauth_headers) - # Send the same credentials via BrowserID - browserid_headers = self._build_browserid_headers(generation=1234, - keys_changed_at=1234, - client_state='aaaa') - res2 = self.app.get("/1.0/sync/1.5", headers=browserid_headers) - # They should get the same node assignment. - self.assertEqual(res1.json["uid"], res2.json["uid"]) - self.assertEqual(res1.json["api_endpoint"], res2.json["api_endpoint"]) - # Earlier generation number via BrowserID -> invalid-generation - browserid_headers = self._build_browserid_headers(generation=1233, - keys_changed_at=1234, - client_state='aaaa') - res = self.app.get("/1.0/sync/1.5", headers=browserid_headers, - status=401) - expected_error_response = { - 'errors': [ - { - 'description': 'Unauthorized', - 'location': 'body', - 'name': '' - } - ], - 'status': 'invalid-generation' - } - self.assertEqual(res.json, expected_error_response) - # Earlier keys_changed_at via BrowserID is not accepted. - browserid_headers = self._build_browserid_headers(generation=1234, - keys_changed_at=1233, - client_state='aaaa') - res = self.app.get("/1.0/sync/1.5", headers=browserid_headers, - status=401) - expected_error_response = { - 'errors': [ - { - 'description': 'Unauthorized', - 'location': 'body', - 'name': '' - } - ], - 'status': 'invalid-keysChangedAt' - } - self.assertEqual(res.json, expected_error_response) - # Earlier generation number via OAuth -> invalid-generation - oauth_headers = self._build_oauth_headers(generation=1233, - keys_changed_at=1234, - client_state='aaaa') - res = self.app.get("/1.0/sync/1.5", headers=oauth_headers, status=401) - expected_error_response = { - 'errors': [ - { - 'description': 'Unauthorized', - 'location': 'body', - 'name': '' - } - ], - 'status': 'invalid-generation' - } - self.assertEqual(res.json, expected_error_response) - # Earlier keys_changed_at via OAuth is not accepted. - oauth_headers = self._build_oauth_headers(generation=1234, - keys_changed_at=1233, - client_state='aaaa') - res = self.app.get("/1.0/sync/1.5", headers=oauth_headers, status=401) - expected_error_response = { - 'errors': [ - { - 'description': 'Unauthorized', - 'location': 'body', - 'name': '' - } - ], - 'status': 'invalid-keysChangedAt' - } - self.assertEqual(res.json, expected_error_response) - # Change client-state via BrowserID. - browserid_headers = self._build_browserid_headers(generation=1235, - keys_changed_at=1235, - client_state='bbbb') - res1 = self.app.get("/1.0/sync/1.5", headers=browserid_headers) - # Previous OAuth creds are rejected due to keys_changed_at update. - oauth_headers = self._build_oauth_headers(generation=1235, - keys_changed_at=1234, - client_state='bbbb') - res = self.app.get("/1.0/sync/1.5", headers=oauth_headers, status=401) - expected_error_response = { - 'errors': [ - { - 'description': 'Unauthorized', - 'location': 'body', - 'name': '' - } - ], - 'status': 'invalid-keysChangedAt' - } - self.assertEqual(res.json, expected_error_response) - # Previous OAuth creds are rejected due to generation update. - oauth_headers = self._build_oauth_headers(generation=1234, - keys_changed_at=1235, - client_state='bbbb') - res = self.app.get("/1.0/sync/1.5", headers=oauth_headers, status=401) - expected_error_response = { - 'errors': [ - { - 'description': 'Unauthorized', - 'location': 'body', - 'name': '' - } - ], - 'status': 'invalid-generation' - } - self.assertEqual(res.json, expected_error_response) - # Updated OAuth creds are accepted. - oauth_headers = self._build_oauth_headers(generation=1235, - keys_changed_at=1235, - client_state='bbbb') - res2 = self.app.get("/1.0/sync/1.5", headers=oauth_headers) - # They should again get the same node assignment. - self.assertEqual(res1.json["uid"], res2.json["uid"]) - self.assertEqual(res1.json["api_endpoint"], - res2.json["api_endpoint"]) - - def test_null_idp_claims(self): - headers = self._build_browserid_headers(generation=1234, - client_state='aaaa') - headers['Authorization'] = headers['Authorization'].replace('1234', - 'null') - # A null fxa-generation claim results in a 401 - res = self.app.get("/1.0/sync/1.5", headers=headers, status=401) - expected_error_response = { - 'errors': [ - { - 'description': 'Unauthorized', - 'location': 'body', - 'name': '' - } - ], - 'status': 'invalid-generation' - } - self.assertEqual(res.json, expected_error_response) - # A null fxa-keysChangedAt claim results in a 401 - headers = self._build_browserid_headers(keys_changed_at=1234, - client_state='aaaa') - headers['Authorization'] = headers['Authorization'].replace('1234', - 'null') - res = self.app.get("/1.0/sync/1.5", headers=headers, status=401) - expected_error_response = { - 'errors': [ - { - 'description': 'invalid keysChangedAt', - 'location': 'body', - 'name': '' - } - ], - 'status': 'invalid-credentials' - } - self.assertEqual(res.json, expected_error_response) - # A null fxa-tokenVerified claim results in a 401 - headers = self._build_browserid_headers(token_verified=True, - client_state='aaaa') - headers['Authorization'] = headers['Authorization'].replace('true', - 'null') - res = self.app.get("/1.0/sync/1.5", headers=headers, status=401) - expected_error_response = { - 'errors': [ - { - 'description': 'Unauthorized', - 'location': 'body', - 'name': '' - } - ], - 'status': 'invalid-credentials' - } - self.assertEqual(res.json, expected_error_response) - headers = self._build_browserid_headers(device_id="device id", - client_state='aaaa') - headers['Authorization'] = \ - headers['Authorization'].replace('"device id"', 'null') - # A null fxa-deviceId claim is acceptable - self.app.get("/1.0/sync/1.5", headers=headers) - - def test_uid_and_kid(self): - browserid_headers = self._build_browserid_headers(user='testuser', - generation=1234, - keys_changed_at=1233, - client_state='aaaa') - res = self.app.get("/1.0/sync/1.5", headers=browserid_headers) - token = self.unsafelyParseToken(res.json["id"]) - self.assertEqual(token["uid"], res.json["uid"]) - self.assertEqual(token["fxa_uid"], "testuser") - self.assertEqual(token["fxa_kid"], "0000000001233-qqo") - self.assertNotEqual(token["hashed_fxa_uid"], token["fxa_uid"]) - self.assertEqual(token["hashed_fxa_uid"], res.json["hashed_fxa_uid"]) - self.assertIn("hashed_device_id", token) - - def test_generation_number_change(self): - headers = self._build_browserid_headers(client_state="aaaa") - # Start with no generation number. - res1 = self.app.get("/1.0/sync/1.5", headers=headers) - # Now send an explicit generation number. - # The node assignment should not change. - headers = self._build_browserid_headers(generation=1234, - client_state="aaaa") - res2 = self.app.get("/1.0/sync/1.5", headers=headers) - self.assertEqual(res1.json["uid"], res2.json["uid"]) - self.assertEqual(res1.json["api_endpoint"], res2.json["api_endpoint"]) - # Clients that don't report generation number are still allowed. - headers = self._build_browserid_headers(client_state="aaaa") - res2 = self.app.get("/1.0/sync/1.5", headers=headers) - self.assertEqual(res1.json["uid"], res2.json["uid"]) - headers = self._build_browserid_headers(device_id="nonsense", - client_state="aaaa") - headers['Authorization'] = \ - headers['Authorization'].replace("fxa-deviceId", "nonsense") - res2 = self.app.get("/1.0/sync/1.5", headers=headers) - self.assertEqual(res1.json["uid"], res2.json["uid"]) - # But previous generation numbers get an invalid-generation response. - headers = self._build_browserid_headers(generation=1233, - client_state="aaaa") - res = self.app.get("/1.0/sync/1.5", headers=headers, status=401) - self.assertEqual(res.json["status"], "invalid-generation") - # Equal generation numbers are accepted. - headers = self._build_browserid_headers(generation=1234, - client_state="aaaa") - res2 = self.app.get("/1.0/sync/1.5", headers=headers) - self.assertEqual(res1.json["uid"], res2.json["uid"]) - self.assertEqual(res1.json["api_endpoint"], res2.json["api_endpoint"]) - # Later generation numbers are accepted. - # Again, the node assignment should not change. - headers = self._build_browserid_headers(generation=1235, - client_state="aaaa") - res2 = self.app.get("/1.0/sync/1.5", headers=headers) - self.assertEqual(res1.json["uid"], res2.json["uid"]) - self.assertEqual(res1.json["api_endpoint"], res2.json["api_endpoint"]) - # And that should lock out the previous generation number - headers = self._build_browserid_headers(generation=1234, - client_state="aaaa") - res = self.app.get("/1.0/sync/1.5", headers=headers, status=401) - self.assertEqual(res.json["status"], "invalid-generation") - - def test_reverting_to_no_keys_changed_at(self): - # Add a user that has no keys_changed_at set - uid = self._add_user(generation=0, keys_changed_at=None, - client_state='aaaa') - # Send a request with keys_changed_at - headers = self._build_browserid_headers(generation=None, - keys_changed_at=1234, - client_state='aaaa') - self.app.get('/1.0/sync/1.5', headers=headers) - user = self._get_user(uid) - # Confirm that keys_changed_at was set - self.assertEqual(user['keys_changed_at'], 1234) - # Send a request with no keys_changed_at - headers = self._build_browserid_headers(generation=None, - keys_changed_at=None, - client_state='aaaa') - # Once a keys_changed_at has been set, the server expects to receive - # it from that point onwards - res = self.app.get('/1.0/sync/1.5', headers=headers, status=401) - expected_error_response = { - 'status': 'invalid-keysChangedAt', - 'errors': [ - { - 'location': 'body', - 'name': '', - 'description': 'Unauthorized', - } - ] - } - self.assertEqual(res.json, expected_error_response) - - def test_zero_keys_changed_at_treated_as_null(self): - # Add a user that has a zero keys_changed_at - uid = self._add_user(generation=0, keys_changed_at=0, - client_state='aaaa') - # Send a request with no keys_changed_at - headers = self._build_browserid_headers(generation=None, - keys_changed_at=None, - client_state='aaaa') - self.app.get('/1.0/sync/1.5', headers=headers) - # The request should succeed and the keys_changed_at should be - # unchanged - user = self._get_user(uid) - self.assertEqual(user['keys_changed_at'], 0) - - def test_reverting_to_no_client_state(self): - # Add a user that has no client_state - uid = self._add_user(generation=0, keys_changed_at=None, - client_state="") - # Send a request with no client state - headers = self._build_browserid_headers(generation=None, - keys_changed_at=None, - client_state=None) - # The request should succeed - self.app.get('/1.0/sync/1.5', headers=headers) - # Send a request that updates the client state - headers = self._build_browserid_headers(generation=None, - keys_changed_at=None, - client_state='aaaa') - # The request should succeed - res = self.app.get('/1.0/sync/1.5', headers=headers) - user = self._get_user(res.json['uid']) - # A new user should have been created - self.assertNotEqual(uid, res.json['uid']) - # The client state should have been updated - self.assertEqual(user['client_state'], 'aaaa') - # Send another request with no client state - headers = self._build_browserid_headers(generation=None, - keys_changed_at=None, - client_state=None) - # The request should fail, since we are trying to revert to using no - # client state after setting one - res = self.app.get('/1.0/sync/1.5', headers=headers, status=401) - expected_error_response = { - 'status': 'invalid-client-state', - 'errors': [ - { - 'location': 'header', - 'name': 'X-Client-State', - 'description': 'Unacceptable client-state value empty ' - 'string', - } - ] - } - self.assertEqual(res.json, expected_error_response) diff --git a/tools/integration_tests/tokenserver/test_e2e.py b/tools/integration_tests/tokenserver/test_e2e.py index 692fd6ef..379ee77a 100644 --- a/tools/integration_tests/tokenserver/test_e2e.py +++ b/tools/integration_tests/tokenserver/test_e2e.py @@ -24,7 +24,6 @@ from tokenserver.test_support import TestCase # This is the client ID used for Firefox Desktop. The FxA team confirmed that # this is the proper client ID to be using for these integration tests. -BROWSERID_AUDIENCE = "https://token.stage.mozaws.net" CLIENT_ID = '5882386c6d801776' DEFAULT_TOKEN_DURATION = 3600 FXA_ACCOUNT_STAGE_HOST = 'https://api-accounts.stage.mozaws.net' @@ -66,8 +65,6 @@ class TestE2e(TestCase, unittest.TestCase): cls.session.verify_email_code(m['headers']['x-verify-code']) # Create an OAuth token to be used for the end-to-end tests cls.oauth_token = cls.oauth_client.authorize_token(cls.session, SCOPE) - cls.browserid_assertion = \ - cls.session.get_identity_assertion(BROWSERID_AUDIENCE) @classmethod def tearDownClass(cls): @@ -76,7 +73,10 @@ class TestE2e(TestCase, unittest.TestCase): # of a race condition, where the record had already been removed. # This causes `destroy_account` to return an error if it attempts # to parse the invalid JSON response. - # This traps for that event. + # It's also possible that the `destroy_account` is rejected due to + # missing authentication. It is not known why the authentication + # is considered missing. + # This traps for those events. try: cls.client.destroy_account(cls.acct.email, cls.fxa_password) except (ServerError, ClientError) as ex: @@ -92,10 +92,6 @@ class TestE2e(TestCase, unittest.TestCase): bad_scope = 'bad_scope' return self.oauth_client.authorize_token(self.session, bad_scope) - def _get_browserid_assertion_with_bad_audience(self): - bad_audience = 'badaudience.com' - return self.session.get_identity_assertion(bad_audience) - def _get_bad_token(self): key = rsa.generate_private_key(backend=default_backend(), public_exponent=65537, @@ -179,34 +175,6 @@ class TestE2e(TestCase, unittest.TestCase): res = self.app.get('/1.0/sync/1.5', headers=headers, status=401) self.assertEqual(res.json, expected_error_response) - def test_unauthorized_browserid_error_status(self): - assertion = self._get_bad_token() - headers = { - 'Authorization': 'BrowserID %s' % assertion, - 'X-Client-State': 'aaaa', - } - # Bad assertion -> 'invalid-credentials' - res = self.app.get('/1.0/sync/1.5', headers=headers, status=401) - expected_error_response = { - 'errors': [ - { - 'description': 'Unauthorized', - 'location': 'body', - 'name': '' - } - ], - 'status': 'invalid-credentials' - } - self.assertEqual(res.json, expected_error_response) - # Bad audience -> 'invalid-credentials' - assertion = self._get_browserid_assertion_with_bad_audience() - headers = { - 'Authorization': 'BrowserID %s' % assertion, - 'X-Client-State': 'aaaa', - } - res = self.app.get('/1.0/sync/1.5', headers=headers, status=401) - self.assertEqual(res.json, expected_error_response) - def test_valid_oauth_request(self): oauth_token = self.oauth_token headers = { @@ -262,56 +230,3 @@ class TestE2e(TestCase, unittest.TestCase): self.assertNotEqual(token["hashed_fxa_uid"], token["fxa_uid"]) self.assertEqual(token["hashed_fxa_uid"], res.json["hashed_fxa_uid"]) self.assertIn("hashed_device_id", token) - - def test_valid_browserid_request(self): - assertion = self.browserid_assertion - headers = { - 'Authorization': 'BrowserID %s' % assertion, - 'X-Client-State': 'aaaa' - } - # Send a valid request, allocating a new user - res = self.app.get('/1.0/sync/1.5', headers=headers) - fxa_uid = self.session.uid - # Retrieve the user from the database - user = self._get_user(res.json['uid']) - # First, let's verify that the token we received is valid. To do this, - # we can unpack the hawk header ID into the payload and its signature - # and then construct a tokenlib token to compute the signature - # ourselves. To obtain a matching signature, we use the same secret as - # is used by Tokenserver. - raw = urlsafe_b64decode(res.json['id']) - payload = raw[:-32] - signature = raw[-32:] - payload_str = payload.decode('utf-8') - - signing_secret = self.TOKEN_SIGNING_SECRET - tm = tokenlib.TokenManager(secret=signing_secret) - expected_signature = tm._get_signature(payload_str.encode('utf8')) - # Using the #compare_digest method here is not strictly necessary, as - # this is not a security-sensitive situation, but it's good practice - self.assertTrue(hmac.compare_digest(expected_signature, signature)) - # Check that the given key is a secret derived from the hawk ID - expected_secret = tokenlib.get_derived_secret( - res.json['id'], secret=signing_secret) - self.assertEqual(res.json['key'], expected_secret) - # Check to make sure the remainder of the fields are valid - self.assertEqual(res.json['uid'], user['uid']) - self.assertEqual(res.json['api_endpoint'], - '%s/1.5/%s' % (self.NODE_URL, user['uid'])) - self.assertEqual(res.json['duration'], DEFAULT_TOKEN_DURATION) - self.assertEqual(res.json['hashalg'], 'sha256') - self.assertEqual(res.json['hashed_fxa_uid'], - self._fxa_metrics_hash(fxa_uid)[:32]) - self.assertEqual(res.json['node_type'], 'spanner') - - token = self.unsafelyParseToken(res.json['id']) - self.assertIn('hashed_device_id', token) - self.assertEqual(token["uid"], res.json["uid"]) - self.assertEqual(token["fxa_uid"], fxa_uid) - assertion = self.browserid_assertion - keys_changed_at = \ - self._extract_keys_changed_at_from_assertion(assertion) - self.assertEqual(token["fxa_kid"], "%s-qqo" % str(keys_changed_at)) - self.assertNotEqual(token["hashed_fxa_uid"], token["fxa_uid"]) - self.assertEqual(token["hashed_fxa_uid"], res.json["hashed_fxa_uid"]) - self.assertIn("hashed_device_id", token) diff --git a/tools/integration_tests/tokenserver/test_support.py b/tools/integration_tests/tokenserver/test_support.py index 4e849d21..982066da 100644 --- a/tools/integration_tests/tokenserver/test_support.py +++ b/tools/integration_tests/tokenserver/test_support.py @@ -17,22 +17,16 @@ DEFAULT_OAUTH_SCOPE = 'https://identity.mozilla.com/apps/oldsync' class TestCase: - BROWSERID_ISSUER = os.environ['SYNC_TOKENSERVER__FXA_BROWSERID_ISSUER'] FXA_EMAIL_DOMAIN = 'api-accounts.stage.mozaws.net' - FXA_METRICS_HASH_SECRET = 'secret0' + FXA_METRICS_HASH_SECRET = os.environ.get("SYNC_MASTER_SECRET", 'secret0') NODE_ID = 800 NODE_URL = 'https://example.com' - TOKEN_SIGNING_SECRET = 'secret0' + TOKEN_SIGNING_SECRET = os.environ.get("SYNC_MASTER_SECRET", 'secret0') TOKENSERVER_HOST = os.environ['TOKENSERVER_HOST'] @classmethod def setUpClass(cls): - cls.auth_method = os.environ['TOKENSERVER_AUTH_METHOD'] - - if cls.auth_method == 'browserid': - cls._build_auth_headers = cls._build_browserid_headers - else: - cls._build_auth_headers = cls._build_oauth_headers + cls._build_auth_headers = cls._build_oauth_headers def setUp(self): engine = create_engine(os.environ['SYNC_TOKENSERVER__DATABASE_URL']) @@ -101,51 +95,6 @@ class TestCase: return headers - def _build_browserid_headers(self, generation=None, user='test', - keys_changed_at=None, client_state=None, - issuer=BROWSERID_ISSUER, device_id=None, - token_verified=None, status=200, - **additional_headers): - claims = { - 'status': 'okay', - 'email': '%s@%s' % (user, self.FXA_EMAIL_DOMAIN), - 'issuer': issuer - } - - if device_id or generation is not None or \ - keys_changed_at is not None or token_verified is not None: - idp_claims = {} - - if device_id: - idp_claims['fxa-deviceId'] = device_id - - if generation: - idp_claims['fxa-generation'] = generation - - if keys_changed_at: - idp_claims['fxa-keysChangedAt'] = keys_changed_at - - if token_verified is not None: - idp_claims['fxa-tokenVerified'] = token_verified - - claims['idpClaims'] = idp_claims - - body = { - 'body': claims, - 'status': status, - } - - headers = { - 'Authorization': 'BrowserID %s' % json.dumps(body), - } - - if client_state: - headers['X-Client-State'] = client_state - - headers.update(additional_headers) - - return headers - def _add_node(self, capacity=100, available=100, node=NODE_URL, id=None, current_load=0, backoff=0, downed=0): query = 'INSERT INTO nodes (service, node, available, capacity, \ diff --git a/tools/tokenserver/loadtests/locustfile.py b/tools/tokenserver/loadtests/locustfile.py index 65afbf95..a3b41fcd 100644 --- a/tools/tokenserver/loadtests/locustfile.py +++ b/tools/tokenserver/loadtests/locustfile.py @@ -2,16 +2,11 @@ from base64 import urlsafe_b64encode as b64encode import binascii import jwt import os -import time -import browserid -import browserid.jwt -from browserid.tests.support import make_assertion from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa from locust import HttpUser, task, between -BROWSERID_AUDIENCE = os.environ['BROWSERID_AUDIENCE'] DEFAULT_OAUTH_SCOPE = 'https://identity.mozilla.com/apps/oldsync' # To create an invalid token, we sign the JWT with a private key that doesn't @@ -25,23 +20,6 @@ INVALID_OAUTH_PRIVATE_KEY = rsa.generate_private_key( # We use a custom mockmyid site to synthesize valid assertions. # It's hosted in a static S3 bucket so we don't swamp the live mockmyid server. MOCKMYID_DOMAIN = "mockmyid.s3-us-west-2.amazonaws.com" -MOCKMYID_PRIVATE_KEY = browserid.jwt.DS128Key({ - "algorithm": "DS", - "x": "385cb3509f086e110c5e24bdd395a84b335a09ae", - "y": "738ec929b559b604a232a9b55a5295afc368063bb9c20fac4e53a74970a4db795" - "6d48e4c7ed523405f629b4cc83062f13029c4d615bbacb8b97f5e56f0c7ac9bc1" - "d4e23809889fa061425c984061fca1826040c399715ce7ed385c4dd0d40225691" - "2451e03452d3c961614eb458f188e3e8d2782916c43dbe2e571251ce38262", - "p": "ff600483db6abfc5b45eab78594b3533d550d9f1bf2a992a7a8daa6dc34f8045a" - "d4e6e0c429d334eeeaaefd7e23d4810be00e4cc1492cba325ba81ff2d5a5b305a" - "8d17eb3bf4a06a349d392e00d329744a5179380344e82a18c47933438f891e22a" - "eef812d69c8f75e326cb70ea000c3f776dfdbd604638c2ef717fc26d02e17", - "q": "e21e04f911d1ed7991008ecaab3bf775984309c3", - "g": "c52a4a0ff3b7e61fdf1867ce84138369a6154f4afa92966e3c827e25cfa6cf508b" - "90e5de419e1337e07a2e9e2a3cd5dea704d175f8ebf6af397d69e110b96afb17c7" - "a03259329e4829b0d03bbc7896b15b4ade53e130858cc34d96269aa89041f40913" - "6c7242a38895c9d5bccad4f389af1d7a4bd1398bd072dffa896233397a", -}) ONE_YEAR = 60 * 60 * 24 * 365 TOKENSERVER_PATH = '/1.0/sync/1.5' @@ -115,46 +93,6 @@ class TokenserverTestUser(HttpUser): self._do_token_exchange_via_oauth(token) - @task(100) - def test_browserid_success(self): - assertion = self._make_browserid_assertion(self.email) - - self._do_token_exchange_via_browserid(assertion) - - @task(3) - def test_expired_browserid_assertion(self): - assertion = self._make_browserid_assertion( - self.email, - exp=int(time.time() - ONE_YEAR) * 1000 - ) - - self._do_token_exchange_via_browserid(assertion, status=401) - - @task(3) - def test_browserid_email_issuer_mismatch(self): - email = "loadtest-%s@%s" % (self.fxa_uid, "hotmail.com") - assertion = self._make_browserid_assertion(email) - - self._do_token_exchange_via_browserid(assertion, status=401) - - @task(3) - def test_browserid_invalid_audience(self): - assertion = self._make_browserid_assertion( - self.email, - audience="http://123done.org" - ) - - self._do_token_exchange_via_browserid(assertion, status=401) - - @task(3) - def test_browserid_invalid_issuer_priv_key(self): - assertion = self._make_browserid_assertion( - self.email, - issuer="api.accounts.firefox.com" - ) - - self._do_token_exchange_via_browserid(assertion, status=401) - def _make_oauth_token(self, email, key=VALID_OAUTH_PRIVATE_KEY, **fields): # For mock oauth tokens, we bundle the desired status code # and response body into a JSON blob for the mock verifier @@ -187,21 +125,6 @@ class TokenserverTestUser(HttpUser): return '%s-%s' % (keys_changed_at, client_state) - def _make_browserid_assertion(self, email, **kwds): - if "audience" not in kwds: - kwds["audience"] = BROWSERID_AUDIENCE - if "exp" not in kwds: - kwds["exp"] = int((time.time() + ONE_YEAR) * 1000) - if "issuer" not in kwds: - kwds["issuer"] = MOCKMYID_DOMAIN - if "issuer_keypair" not in kwds: - kwds["issuer_keypair"] = (None, MOCKMYID_PRIVATE_KEY) - kwds["idp_claims"] = { - 'fxa-generation': self.generation_counter, - 'fxa-keysChangedAt': self.generation_counter, - } - return make_assertion(email, **kwds) - def _do_token_exchange_via_oauth(self, token, status=200): headers = { 'Authorization': 'Bearer %s' % token, @@ -213,15 +136,3 @@ class TokenserverTestUser(HttpUser): headers=headers) as res: if res.status_code == status: res.success() - - def _do_token_exchange_via_browserid(self, assertion, status=200): - headers = { - 'Authorization': 'BrowserID %s' % assertion, - 'X-Client-State': self.client_state - } - - with self.client.get(TOKENSERVER_PATH, - catch_response=True, - headers=headers) as res: - if res.status_code == status: - res.success() diff --git a/tools/tokenserver/util.py b/tools/tokenserver/util.py index c87857bd..12dda9fd 100644 --- a/tools/tokenserver/util.py +++ b/tools/tokenserver/util.py @@ -10,13 +10,16 @@ Admin/managment scripts for TokenServer. import sys import time import logging +import base64 import os import json from datetime import datetime from datadog import initialize, statsd -from browserid.utils import encode_bytes as encode_bytes_b64 + +def encode_bytes_b64(value): + return base64.urlsafe_b64encode(value).rstrip(b'=').decode('ascii') def run_script(main):