Merge branch 'master' into omegazord
This commit is contained in:
Коммит
4eb950f21b
32
CHANGELOG.md
32
CHANGELOG.md
|
@ -1,3 +1,35 @@
|
|||
# v0.34.0 (_2019-07-10_)
|
||||
|
||||
[Full Changelog](https://github.com/mozilla/application-services/compare/v0.33.2...v0.34.0)
|
||||
|
||||
## General
|
||||
|
||||
- All of our cryptographic primitives are now backed by NSS ([#1349](https://github.com/mozilla/application-services/pull/1349)). This change should be transparent our customers.
|
||||
|
||||
If you build application-services, it is recommended to delete the `libs/{desktop, ios, android}` folders and start over using `./build-all.sh [android|desktop|ios]`. [GYP](https://github.com/mogemimi/pomdog/wiki/How-to-Install-GYP) and [ninja](https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages) are required to build these libraries.
|
||||
|
||||
## Places
|
||||
|
||||
### What's New
|
||||
|
||||
- Added `WritableHistoryConnection.acceptResult(searchString, url)` for marking
|
||||
an awesomebar result as accepted.
|
||||
([#1332](https://github.com/mozilla/application-services/pull/1332))
|
||||
- Specifically, `queryAutocomplete` calls for searches that contain
|
||||
frequently accepted results are more highly ranked.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- Android only: The addition of `acceptResult` to `WritableHistoryConnection` is
|
||||
a breaking change for any custom implementations of `WritableHistoryConnection`
|
||||
([#1332](https://github.com/mozilla/application-services/pull/1332))
|
||||
|
||||
## Push
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- `OpenSSLError` has been renamed to the more general `CryptoError`. ([#1349](https://github.com/mozilla/application-services/pull/1349))
|
||||
|
||||
# v0.33.2 (_2019-07-04_)
|
||||
|
||||
[Full Changelog](https://github.com/mozilla/application-services/compare/v0.33.1...v0.33.2)
|
||||
|
|
|
@ -2,31 +2,4 @@
|
|||
|
||||
# Unreleased Changes
|
||||
|
||||
[Full Changelog](https://github.com/mozilla/application-services/compare/v0.33.2...master)
|
||||
|
||||
## General
|
||||
|
||||
- All of our cryptographic primitives are now backed by NSS. This change should be transparent our customers.
|
||||
If you build application-services, it is recommended to delete the `libs/{desktop, ios, android}` folders and start over using `./build-all.sh [android|desktop|ios]`.
|
||||
|
||||
## Places
|
||||
|
||||
### What's New
|
||||
|
||||
- Added `WritableHistoryConnection.acceptResult(searchString, url)` for marking
|
||||
an awesomebar result as accepted.
|
||||
([#1332](https://github.com/mozilla/application-services/pull/1332))
|
||||
- Specifically, `queryAutocomplete` calls for searches that contain
|
||||
frequently accepted results are more highly ranked.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- Android only: The addition of `acceptResult` to `WritableHistoryConnection` is
|
||||
a breaking change for any custom implementations of `WritableHistoryConnection`
|
||||
([#1332](https://github.com/mozilla/application-services/pull/1332))
|
||||
|
||||
## Push
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- `OpenSSLError` has been renamed to the more general `CryptoError`.
|
||||
[Full Changelog](https://github.com/mozilla/application-services/compare/v0.34.0...master)
|
||||
|
|
|
@ -112,7 +112,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.49.2"
|
||||
version = "0.50.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -123,7 +123,7 @@ dependencies = [
|
|||
"env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -288,7 +288,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fxa-client 0.1.0",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sync15 0.1.0",
|
||||
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"webbrowser 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -361,7 +361,7 @@ dependencies = [
|
|||
"cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"publicsuffix 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -571,7 +571,7 @@ name = "dogear"
|
|||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallbitvec 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -621,7 +621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
dependencies = [
|
||||
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
@ -697,7 +697,7 @@ dependencies = [
|
|||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
@ -709,7 +709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
dependencies = [
|
||||
"dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -800,7 +800,7 @@ dependencies = [
|
|||
"force-viaduct-reqwest 0.1.0",
|
||||
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mockiato 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl 0.10.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -824,7 +824,7 @@ dependencies = [
|
|||
"ffi-support 0.3.4",
|
||||
"fxa-client 0.1.0",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"viaduct 0.1.0",
|
||||
|
@ -871,7 +871,7 @@ dependencies = [
|
|||
"futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -968,7 +968,7 @@ dependencies = [
|
|||
"httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1114,7 +1114,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.6"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1135,7 +1135,7 @@ dependencies = [
|
|||
"fxa-client 0.1.0",
|
||||
"interrupt 0.1.0",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"more-asserts 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"prettytable-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rusqlite 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1154,7 +1154,7 @@ dependencies = [
|
|||
"base16 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ffi-support 0.3.4",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"logins 0.1.0",
|
||||
"rusqlite 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1270,7 +1270,7 @@ dependencies = [
|
|||
"iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1320,7 +1320,7 @@ dependencies = [
|
|||
"difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1348,7 +1348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
dependencies = [
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl 0.10.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl-sys 0.9.47 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1416,7 +1416,7 @@ dependencies = [
|
|||
name = "nss_sys"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bindgen 0.49.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bindgen 0.50.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"toml 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1645,7 +1645,7 @@ dependencies = [
|
|||
"idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"interrupt 0.1.0",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"more-asserts 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1659,6 +1659,7 @@ dependencies = [
|
|||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sql-support 0.1.0",
|
||||
"structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sync-guid 0.1.0",
|
||||
"sync15 0.1.0",
|
||||
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1675,11 +1676,12 @@ dependencies = [
|
|||
"ffi-support 0.3.4",
|
||||
"interrupt 0.1.0",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"places 0.1.0",
|
||||
"prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sql-support 0.1.0",
|
||||
"sync-guid 0.1.0",
|
||||
"sync15 0.1.0",
|
||||
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"viaduct 0.1.0",
|
||||
|
@ -1740,7 +1742,7 @@ dependencies = [
|
|||
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"multimap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1796,7 +1798,7 @@ dependencies = [
|
|||
"force-viaduct-reqwest 0.1.0",
|
||||
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mockito 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rc_crypto 0.1.0",
|
||||
"rusqlite 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1815,7 +1817,7 @@ dependencies = [
|
|||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ffi-support 0.3.4",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"push 0.1.0",
|
||||
"rusqlite 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2049,7 +2051,7 @@ dependencies = [
|
|||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ffi-support 0.3.4",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2135,7 +2137,7 @@ dependencies = [
|
|||
"http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hyper 0.12.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mime_guess 2.0.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2304,6 +2306,14 @@ dependencies = [
|
|||
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_test"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.5.5"
|
||||
|
@ -2363,7 +2373,7 @@ dependencies = [
|
|||
"ffi-support 0.3.4",
|
||||
"interrupt 0.1.0",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rusqlite 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -2420,6 +2430,17 @@ dependencies = [
|
|||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync-guid"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rc_crypto 0.1.0",
|
||||
"rusqlite 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_test 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync-test"
|
||||
version = "0.1.0"
|
||||
|
@ -2428,7 +2449,7 @@ dependencies = [
|
|||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fxa-client 0.1.0",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"logins 0.1.0",
|
||||
"rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2447,7 +2468,7 @@ dependencies = [
|
|||
"ffi-support 0.3.4",
|
||||
"interrupt 0.1.0",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rc_crypto 0.1.0",
|
||||
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2614,7 +2635,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
dependencies = [
|
||||
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2625,7 +2646,7 @@ dependencies = [
|
|||
"crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2666,7 +2687,7 @@ dependencies = [
|
|||
"crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2823,7 +2844,7 @@ dependencies = [
|
|||
"failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ffi-support 0.3.4",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"prost-build 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"prost-derive 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2854,7 +2875,7 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -2962,7 +2983,7 @@ dependencies = [
|
|||
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
|
||||
"checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
|
||||
"checksum bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9f04a5e50dc80b3d5d35320889053637d15011aed5e66b66b37ae798c65da6f7"
|
||||
"checksum bindgen 0.49.2 (registry+https://github.com/rust-lang/crates.io-index)" = "846a1fba6535362a01487ef6b10f0275faa12e5c5d835c5c1c627aabc46ccbd6"
|
||||
"checksum bindgen 0.50.0 (registry+https://github.com/rust-lang/crates.io-index)" = "65a913de3fa2fa95f2c593bb7e33b1be1ce1ce8a83f34b6bb02e6f01400b96cc"
|
||||
"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
|
||||
"checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400"
|
||||
"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
|
||||
|
@ -3061,7 +3082,7 @@ dependencies = [
|
|||
"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
|
||||
"checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c"
|
||||
"checksum lock_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed946d4529956a20f2d63ebe1b69996d5a2137c91913fe3ebbeff957f5bca7ff"
|
||||
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
|
||||
"checksum log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c275b6ad54070ac2d665eef9197db647b32239c9d244bfb6f041a766d00da5b3"
|
||||
"checksum lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
|
||||
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
||||
"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a"
|
||||
|
@ -3168,6 +3189,7 @@ dependencies = [
|
|||
"checksum serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)" = "076a696fdea89c19d3baed462576b8f6d663064414b5c793642da8dfeb99475b"
|
||||
"checksum serde_derive 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)" = "ef45eb79d6463b22f5f9e16d283798b7c0175ba6050bc25c1a946c122727fe7b"
|
||||
"checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704"
|
||||
"checksum serde_test 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)" = "6e5889093c6aa447d971c1f152c1127eb96ef0a53c4a3e3082158fe6670b28c6"
|
||||
"checksum serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a"
|
||||
"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d"
|
||||
"checksum shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
|
||||
|
|
|
@ -13,6 +13,7 @@ members = [
|
|||
"components/support/error",
|
||||
"components/support/ffi",
|
||||
"components/support/force-viaduct-reqwest",
|
||||
"components/support/guid",
|
||||
"components/support/interrupt",
|
||||
"components/support/rc_crypto",
|
||||
"components/support/rc_crypto/nss",
|
||||
|
|
|
@ -23,7 +23,7 @@ sync15 = { path = "../sync15" }
|
|||
url = "1.7.1"
|
||||
ffi-support = { path = "../support/ffi" }
|
||||
viaduct = { path = "../viaduct" }
|
||||
rc_crypto = { path = "../support/rc_crypto", features = ["ece"] }
|
||||
rc_crypto = { path = "../support/rc_crypto", features = ["ece", "hawk"] }
|
||||
error-support = { path = "../support/error" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -38,6 +38,5 @@ mockiato = "0.8.0"
|
|||
prost-build = "0.5"
|
||||
|
||||
[features]
|
||||
browserid = ["openssl", "rc_crypto/hawk"]
|
||||
reqwest = ["viaduct/reqwest"]
|
||||
default = []
|
||||
|
|
|
@ -11,7 +11,7 @@ crate-type = ["lib"]
|
|||
|
||||
[dependencies]
|
||||
ffi-support = { path = "../../support/ffi" }
|
||||
log = "0.4.6"
|
||||
log = "0.4.7"
|
||||
lazy_static = "1.3.0"
|
||||
url = "1.7.1"
|
||||
prost = "0.5.0"
|
||||
|
@ -21,5 +21,4 @@ viaduct = { path = "../../viaduct" }
|
|||
path = "../"
|
||||
|
||||
[features]
|
||||
browserid = ["fxa-client/browserid"]
|
||||
reqwest = ["viaduct/reqwest", "fxa-client/reqwest"]
|
||||
|
|
|
@ -233,7 +233,7 @@ pub extern "C" fn fxa_complete_oauth_flow(
|
|||
});
|
||||
}
|
||||
|
||||
/// Migrate from a logged-in browserid Firefox Account.
|
||||
/// Migrate from a logged-in sessionToken Firefox Account.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fxa_migrate_from_session_token(
|
||||
handle: u64,
|
||||
|
|
|
@ -1,128 +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/. */
|
||||
|
||||
use crate::{
|
||||
error::*,
|
||||
http_client::browser_id::jwt_utils,
|
||||
login_sm::{LoginState, LoginStateMachine, MarriedState, ReadyForKeysState, SessionTokenState},
|
||||
Config, FirefoxAccount, StateV2,
|
||||
};
|
||||
use serde_derive::*;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
impl FirefoxAccount {
|
||||
// Initialize state from Firefox Accounts credentials obtained using the
|
||||
// web flow.
|
||||
pub fn from_credentials(
|
||||
content_url: &str,
|
||||
client_id: &str,
|
||||
redirect_uri: &str,
|
||||
credentials: WebChannelResponse,
|
||||
) -> Result<Self> {
|
||||
let config = Config::new(content_url, client_id, redirect_uri);
|
||||
let session_token = hex::decode(credentials.session_token)?;
|
||||
let key_fetch_token = hex::decode(credentials.key_fetch_token)?;
|
||||
let unwrap_kb = hex::decode(credentials.unwrap_kb)?;
|
||||
let login_state_data = ReadyForKeysState::new(
|
||||
credentials.uid,
|
||||
credentials.email,
|
||||
session_token,
|
||||
key_fetch_token,
|
||||
unwrap_kb,
|
||||
);
|
||||
let login_state = if credentials.verified {
|
||||
LoginState::EngagedAfterVerified(login_state_data)
|
||||
} else {
|
||||
LoginState::EngagedBeforeVerified(login_state_data)
|
||||
};
|
||||
|
||||
Ok(Self::from_state(StateV2 {
|
||||
config,
|
||||
login_state,
|
||||
refresh_token: None,
|
||||
scoped_keys: HashMap::new(),
|
||||
last_handled_command: None,
|
||||
commands_data: HashMap::new(),
|
||||
device_capabilities: HashSet::new(),
|
||||
session_token: None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn advance_to_married(&mut self) -> Result<Option<&MarriedState>> {
|
||||
self.advance()?;
|
||||
match self.state.login_state {
|
||||
LoginState::Married(ref married) => Ok(Some(married)),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self) -> Result<()> {
|
||||
let state_machine = LoginStateMachine::new(&self.state.config, self.client.clone());
|
||||
let state = std::mem::replace(&mut self.state.login_state, LoginState::Unknown);
|
||||
self.state.login_state = state_machine.advance(state)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn session_token_from_state(state: &LoginState) -> Option<&[u8]> {
|
||||
match state {
|
||||
&LoginState::Separated(_) | LoginState::Unknown => None,
|
||||
// Despite all these states implementing the same trait we can't treat
|
||||
// them in a single arm, so this will do for now :/
|
||||
&LoginState::EngagedBeforeVerified(ref state)
|
||||
| &LoginState::EngagedAfterVerified(ref state) => Some(state.session_token()),
|
||||
&LoginState::CohabitingBeforeKeyPair(ref state) => Some(state.session_token()),
|
||||
&LoginState::CohabitingAfterKeyPair(ref state) => Some(state.session_token()),
|
||||
&LoginState::Married(ref state) => Some(state.session_token()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_assertion(&mut self, audience: &str) -> Result<String> {
|
||||
let married = match self.advance_to_married()? {
|
||||
Some(married) => married,
|
||||
None => return Err(ErrorKind::NotMarried.into()),
|
||||
};
|
||||
let key_pair = married.key_pair();
|
||||
let certificate = married.certificate();
|
||||
Ok(jwt_utils::create_assertion(
|
||||
key_pair,
|
||||
&certificate,
|
||||
audience,
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn get_sync_keys(&mut self) -> Result<SyncKeys> {
|
||||
let married = match self.advance_to_married()? {
|
||||
Some(married) => married,
|
||||
None => return Err(ErrorKind::NotMarried.into()),
|
||||
};
|
||||
let sync_key = hex::encode(married.sync_key());
|
||||
Ok(SyncKeys(sync_key, married.xcs().to_string()))
|
||||
}
|
||||
|
||||
pub fn sign_out(mut self) {
|
||||
self.client.sign_out();
|
||||
self.state.login_state = self.state.login_state.into_separated();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct WebChannelResponse {
|
||||
uid: String,
|
||||
email: String,
|
||||
verified: bool,
|
||||
#[serde(rename = "sessionToken")]
|
||||
session_token: String,
|
||||
#[serde(rename = "keyFetchToken")]
|
||||
key_fetch_token: String,
|
||||
#[serde(rename = "unwrapBKey")]
|
||||
unwrap_kb: String,
|
||||
}
|
||||
|
||||
impl WebChannelResponse {
|
||||
pub fn from_json(json: &str) -> Result<WebChannelResponse> {
|
||||
serde_json::from_str(json).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SyncKeys(pub String, pub String);
|
|
@ -117,10 +117,6 @@ pub enum ErrorKind {
|
|||
#[fail(display = "Hex decode error: {}", _0)]
|
||||
HexDecodeError(#[fail(cause)] hex::FromHexError),
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
#[fail(display = "OpenSSL error: {}", _0)]
|
||||
OpensslError(#[fail(cause)] openssl::error::ErrorStack),
|
||||
|
||||
#[fail(display = "Base64 decode error: {}", _0)]
|
||||
Base64Decode(#[fail(cause)] base64::DecodeError),
|
||||
|
||||
|
@ -170,10 +166,3 @@ error_support::define_error_conversions! {
|
|||
(HawkError, hawk::Error),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
error_support::define_error_conversions! {
|
||||
ErrorKind {
|
||||
(OpensslError, openssl::error::ErrorStack),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,39 +3,49 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::{config::Config, error::*};
|
||||
use browser_id::{derive_hawk_auth_key_from_session_token, hawk_request::HawkRequestBuilder};
|
||||
use hex;
|
||||
use rc_crypto::hawk::{Credentials, Key, PayloadHasher, RequestBuilder, SHA256};
|
||||
use rc_crypto::{digest, hkdf, hmac};
|
||||
use serde_derive::*;
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use url::Url;
|
||||
use viaduct::{header_names, status_codes, Method, Request, Response};
|
||||
|
||||
pub(crate) mod browser_id;
|
||||
const HAWK_HKDF_SALT: [u8; 32] = [0b0; 32];
|
||||
const HAWK_KEY_LENGTH: usize = 32;
|
||||
|
||||
#[cfg_attr(test, mockiato::mockable)]
|
||||
pub trait FxAClient {
|
||||
fn oauth_token_with_code(
|
||||
fn oauth_tokens_from_code(
|
||||
&self,
|
||||
config: &Config,
|
||||
code: &str,
|
||||
code_verifier: &str,
|
||||
) -> Result<OAuthTokenResponse>;
|
||||
fn oauth_tokens_from_session_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &str,
|
||||
scopes: &[&str],
|
||||
) -> Result<OAuthTokenResponse>;
|
||||
fn oauth_token_with_refresh_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
refresh_token: &str,
|
||||
scopes: &[&str],
|
||||
) -> Result<OAuthTokenResponse>;
|
||||
fn oauth_token_with_session_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &str,
|
||||
scopes: &[&str],
|
||||
) -> Result<OAuthTokenResponse>;
|
||||
fn duplicate_session(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &[u8],
|
||||
session_token: &str,
|
||||
) -> Result<DuplicateTokenResponse>;
|
||||
fn oauth_token_from_session_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &[u8],
|
||||
scopes: &[&str],
|
||||
) -> Result<OAuthTokenResponse>;
|
||||
fn destroy_oauth_token(&self, config: &Config, token: &str) -> Result<()>;
|
||||
fn profile(
|
||||
&self,
|
||||
|
@ -69,7 +79,7 @@ pub trait FxAClient {
|
|||
fn scoped_key_data(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &[u8],
|
||||
session_token: &str,
|
||||
scope: &str,
|
||||
) -> Result<HashMap<String, ScopedKeyDataResponse>>;
|
||||
}
|
||||
|
@ -102,7 +112,9 @@ impl FxAClient for Client {
|
|||
}))
|
||||
}
|
||||
|
||||
fn oauth_token_with_code(
|
||||
// For the one-off generation of a `refresh_token` and associated meta from transient credentials.
|
||||
|
||||
fn oauth_tokens_from_code(
|
||||
&self,
|
||||
config: &Config,
|
||||
code: &str,
|
||||
|
@ -116,6 +128,28 @@ impl FxAClient for Client {
|
|||
self.make_oauth_token_request(config, body)
|
||||
}
|
||||
|
||||
fn oauth_tokens_from_session_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &str,
|
||||
scopes: &[&str],
|
||||
) -> Result<OAuthTokenResponse> {
|
||||
let url = config.token_endpoint()?;
|
||||
let key = derive_auth_key_from_session_token(&session_token)?;
|
||||
let body = json!({
|
||||
"client_id": config.client_id,
|
||||
"scope": scopes.join(" "),
|
||||
"grant_type": "fxa-credentials",
|
||||
"access_type": "offline",
|
||||
});
|
||||
let request = HawkRequestBuilder::new(Method::Post, url, &key)
|
||||
.body(body)
|
||||
.build()?;
|
||||
Ok(Self::make_request(request)?.json()?)
|
||||
}
|
||||
|
||||
// For the regular generation of an `access_token` from long-lived credentials.
|
||||
|
||||
fn oauth_token_with_refresh_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
|
@ -131,13 +165,32 @@ impl FxAClient for Client {
|
|||
self.make_oauth_token_request(config, body)
|
||||
}
|
||||
|
||||
fn oauth_token_with_session_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &str,
|
||||
scopes: &[&str],
|
||||
) -> Result<OAuthTokenResponse> {
|
||||
let parameters = json!({
|
||||
"client_id": config.client_id,
|
||||
"grant_type": "fxa-credentials",
|
||||
"scope": scopes.join(" ")
|
||||
});
|
||||
let key = derive_auth_key_from_session_token(session_token)?;
|
||||
let url = config.token_endpoint()?;
|
||||
let request = HawkRequestBuilder::new(Method::Post, url, &key)
|
||||
.body(parameters)
|
||||
.build()?;
|
||||
Self::make_request(request)?.json().map_err(Into::into)
|
||||
}
|
||||
|
||||
fn duplicate_session(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &[u8],
|
||||
session_token: &str,
|
||||
) -> Result<DuplicateTokenResponse> {
|
||||
let url = config.auth_url_path("v1/session/duplicate")?;
|
||||
let key = derive_hawk_auth_key_from_session_token(&session_token)?;
|
||||
let key = derive_auth_key_from_session_token(&session_token)?;
|
||||
let duplicate_body = json!({
|
||||
"reason": "migration"
|
||||
});
|
||||
|
@ -148,26 +201,6 @@ impl FxAClient for Client {
|
|||
Ok(Self::make_request(request)?.json()?)
|
||||
}
|
||||
|
||||
fn oauth_token_from_session_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &[u8],
|
||||
scopes: &[&str],
|
||||
) -> Result<OAuthTokenResponse> {
|
||||
let url = config.auth_url_path("v1/oauth/token")?;
|
||||
let key = derive_hawk_auth_key_from_session_token(&session_token)?;
|
||||
let body = json!({
|
||||
"client_id": config.client_id,
|
||||
"scope": scopes.join(" "),
|
||||
"grant_type": "fxa-credentials",
|
||||
"access_type": "offline",
|
||||
});
|
||||
let request = HawkRequestBuilder::new(Method::Post, url, &key)
|
||||
.body(body)
|
||||
.build()?;
|
||||
Ok(Self::make_request(request)?.json()?)
|
||||
}
|
||||
|
||||
fn destroy_oauth_token(&self, config: &Config, token: &str) -> Result<()> {
|
||||
let body = json!({
|
||||
"token": token,
|
||||
|
@ -254,7 +287,7 @@ impl FxAClient for Client {
|
|||
fn scoped_key_data(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &[u8],
|
||||
session_token: &str,
|
||||
scope: &str,
|
||||
) -> Result<HashMap<String, ScopedKeyDataResponse>> {
|
||||
let body = json!({
|
||||
|
@ -262,7 +295,7 @@ impl FxAClient for Client {
|
|||
"scope": scope,
|
||||
});
|
||||
let url = config.auth_url_path("v1/account/scoped-key-data")?;
|
||||
let key = derive_hawk_auth_key_from_session_token(session_token)?;
|
||||
let key = derive_auth_key_from_session_token(session_token)?;
|
||||
let request = HawkRequestBuilder::new(Method::Post, url, &key)
|
||||
.body(body)
|
||||
.build()?;
|
||||
|
@ -309,6 +342,79 @@ fn bearer_token(token: &str) -> String {
|
|||
format!("Bearer {}", token)
|
||||
}
|
||||
|
||||
fn kw(name: &str) -> Vec<u8> {
|
||||
format!("identity.mozilla.com/picl/v1/{}", name)
|
||||
.as_bytes()
|
||||
.to_vec()
|
||||
}
|
||||
|
||||
pub fn derive_auth_key_from_session_token(session_token: &str) -> Result<Vec<u8>> {
|
||||
let session_token_bytes = hex::decode(session_token)?;
|
||||
let context_info = kw("sessionToken");
|
||||
let salt = hmac::SigningKey::new(&digest::SHA256, &HAWK_HKDF_SALT);
|
||||
let mut out = vec![0u8; HAWK_KEY_LENGTH * 2];
|
||||
hkdf::extract_and_expand(&salt, &session_token_bytes, &context_info, &mut out)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
struct HawkRequestBuilder<'a> {
|
||||
url: Url,
|
||||
method: Method,
|
||||
body: Option<String>,
|
||||
hkdf_sha256_key: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> HawkRequestBuilder<'a> {
|
||||
pub fn new(method: Method, url: Url, hkdf_sha256_key: &'a [u8]) -> Self {
|
||||
rc_crypto::ensure_initialized();
|
||||
HawkRequestBuilder {
|
||||
url,
|
||||
method,
|
||||
body: None,
|
||||
hkdf_sha256_key,
|
||||
}
|
||||
}
|
||||
|
||||
// This class assumes that the content being sent it always of the type
|
||||
// application/json.
|
||||
pub fn body(mut self, body: serde_json::Value) -> Self {
|
||||
self.body = Some(body.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
fn make_hawk_header(&self) -> Result<String> {
|
||||
// Make sure we de-allocate the hash after hawk_request_builder.
|
||||
let hash;
|
||||
let method = format!("{}", self.method);
|
||||
let mut hawk_request_builder = RequestBuilder::from_url(method.as_str(), &self.url)?;
|
||||
if let Some(ref body) = self.body {
|
||||
hash = PayloadHasher::hash("application/json", SHA256, &body)?;
|
||||
hawk_request_builder = hawk_request_builder.hash(&hash[..]);
|
||||
}
|
||||
let hawk_request = hawk_request_builder.request();
|
||||
let token_id = hex::encode(&self.hkdf_sha256_key[0..HAWK_KEY_LENGTH]);
|
||||
let hmac_key = &self.hkdf_sha256_key[HAWK_KEY_LENGTH..(2 * HAWK_KEY_LENGTH)];
|
||||
let hawk_credentials = Credentials {
|
||||
id: token_id,
|
||||
key: Key::new(hmac_key, SHA256)?,
|
||||
};
|
||||
let header = hawk_request.make_header(&hawk_credentials)?;
|
||||
Ok(format!("Hawk {}", header))
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<Request> {
|
||||
let hawk_header = self.make_hawk_header()?;
|
||||
let mut request =
|
||||
Request::new(self.method, self.url).header(header_names::AUTHORIZATION, hawk_header)?;
|
||||
if let Some(body) = self.body {
|
||||
request = request
|
||||
.header(header_names::CONTENT_TYPE, "application/json")?
|
||||
.body(body);
|
||||
}
|
||||
Ok(request)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ResponseAndETag<T> {
|
||||
pub response: T,
|
||||
|
|
|
@ -1,335 +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/. */
|
||||
|
||||
use crate::error::*;
|
||||
#[cfg(feature = "browserid")]
|
||||
use crate::{
|
||||
http_client::{self, browser_id::hawk_request::HawkRequestBuilder, OAuthTokenResponse},
|
||||
util::Xorable,
|
||||
Config,
|
||||
};
|
||||
use rc_crypto::{digest, hkdf, hmac};
|
||||
#[cfg(feature = "browserid")]
|
||||
use rsa::RSABrowserIDKeyPair;
|
||||
#[cfg(feature = "browserid")]
|
||||
use serde_derive::*;
|
||||
#[cfg(feature = "browserid")]
|
||||
use serde_json::json;
|
||||
#[cfg(feature = "browserid")]
|
||||
use url::Url;
|
||||
#[cfg(feature = "browserid")]
|
||||
use viaduct::{Method, Request};
|
||||
pub(crate) mod hawk_request;
|
||||
#[cfg(feature = "browserid")]
|
||||
pub(crate) mod jwt_utils;
|
||||
#[cfg(feature = "browserid")]
|
||||
pub(crate) mod rsa;
|
||||
|
||||
const HKDF_SALT: [u8; 32] = [0b0; 32];
|
||||
const KEY_LENGTH: usize = 32;
|
||||
#[cfg(feature = "browserid")]
|
||||
const SIGN_DURATION_MS: u64 = 24 * 60 * 60 * 1000;
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
pub trait BrowserIDKeyPair {
|
||||
fn get_algo(&self) -> String;
|
||||
fn sign(&self, message: &[u8]) -> Result<Vec<u8>>;
|
||||
fn verify_message(&self, message: &[u8], signature: &[u8]) -> Result<bool>;
|
||||
fn to_json(&self, include_private: bool) -> Result<serde_json::Value>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
pub trait FxABrowserIDClient: http_client::FxAClient {
|
||||
fn sign_out(&self);
|
||||
fn login(
|
||||
&self,
|
||||
config: &Config,
|
||||
email: &str,
|
||||
auth_pwd: &str,
|
||||
get_keys: bool,
|
||||
) -> Result<LoginResponse>;
|
||||
fn account_status(&self, config: &Config, uid: &str) -> Result<AccountStatusResponse>;
|
||||
fn keys(&self, config: &Config, key_fetch_token: &[u8]) -> Result<KeysResponse>;
|
||||
fn recovery_email_status(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &[u8],
|
||||
) -> Result<RecoveryEmailStatusResponse>;
|
||||
fn oauth_token_with_session_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &[u8],
|
||||
scopes: &[&str],
|
||||
) -> Result<OAuthTokenResponse>;
|
||||
fn sign(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &[u8],
|
||||
key_pair: &dyn BrowserIDKeyPair,
|
||||
) -> Result<SignResponse>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
impl FxABrowserIDClient for http_client::Client {
|
||||
fn sign_out(&self) {
|
||||
panic!("Not implemented yet!");
|
||||
}
|
||||
|
||||
fn login(
|
||||
&self,
|
||||
config: &Config,
|
||||
email: &str,
|
||||
auth_pwd: &str,
|
||||
get_keys: bool,
|
||||
) -> Result<LoginResponse> {
|
||||
let url = config.auth_url_path("v1/account/login")?;
|
||||
let parameters = json!({
|
||||
"email": email,
|
||||
"authPW": auth_pwd
|
||||
});
|
||||
let request = Request::post(url)
|
||||
.query(&[("keys", if get_keys { "true" } else { "false" })])
|
||||
.json(¶meters);
|
||||
Self::make_request(request)?.json().map_err(Into::into)
|
||||
}
|
||||
|
||||
fn account_status(&self, config: &Config, uid: &str) -> Result<AccountStatusResponse> {
|
||||
let url = config.auth_url_path("v1/account/status")?;
|
||||
let request = Request::get(url).query(&[("uid", uid)]);
|
||||
Self::make_request(request)?.json().map_err(Into::into)
|
||||
}
|
||||
|
||||
fn keys(&self, config: &Config, key_fetch_token: &[u8]) -> Result<KeysResponse> {
|
||||
let url = config.auth_url_path("v1/account/keys")?;
|
||||
let context_info = kw("keyFetchToken");
|
||||
let key =
|
||||
derive_hkdf_sha256_key(&key_fetch_token, &HKDF_SALT, &context_info, KEY_LENGTH * 3)?;
|
||||
let key_request_key = &key[(KEY_LENGTH * 2)..(KEY_LENGTH * 3)];
|
||||
let request = HawkRequestBuilder::new(Method::Get, url, &key).build()?;
|
||||
let json: serde_json::Value = Self::make_request(request)?.json()?;
|
||||
let bundle = match json["bundle"].as_str() {
|
||||
Some(bundle) => bundle,
|
||||
None => panic!("Invalid JSON"),
|
||||
};
|
||||
let data = hex::decode(bundle)?;
|
||||
if data.len() != 3 * KEY_LENGTH {
|
||||
return Err(ErrorKind::BadKeyLength("bundle", 3 * KEY_LENGTH, data.len()).into());
|
||||
}
|
||||
let ciphertext = &data[0..(KEY_LENGTH * 2)];
|
||||
let mac_code = &data[(KEY_LENGTH * 2)..(KEY_LENGTH * 3)];
|
||||
let context_info = kw("account/keys");
|
||||
let bytes =
|
||||
derive_hkdf_sha256_key(key_request_key, &HKDF_SALT, &context_info, KEY_LENGTH * 3)?;
|
||||
let hmac_key = &bytes[0..KEY_LENGTH];
|
||||
let xor_key = &bytes[KEY_LENGTH..(KEY_LENGTH * 3)];
|
||||
|
||||
let v_key = hmac::VerificationKey::new(&digest::SHA256, hmac_key);
|
||||
hmac::verify(&v_key, ciphertext, mac_code).map_err(|_| ErrorKind::HmacMismatch)?;
|
||||
|
||||
let xored_bytes = ciphertext.xored_with(xor_key)?;
|
||||
let wrap_kb = xored_bytes[KEY_LENGTH..(KEY_LENGTH * 2)].to_vec();
|
||||
Ok(KeysResponse { wrap_kb })
|
||||
}
|
||||
|
||||
fn recovery_email_status(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &[u8],
|
||||
) -> Result<RecoveryEmailStatusResponse> {
|
||||
let url = config.auth_url_path("v1/recovery_email/status")?;
|
||||
let key = derive_hawk_auth_key_from_session_token(session_token)?;
|
||||
let request = HawkRequestBuilder::new(Method::Get, url, &key).build()?;
|
||||
Self::make_request(request)?.json().map_err(Into::into)
|
||||
}
|
||||
|
||||
fn oauth_token_with_session_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &[u8],
|
||||
scopes: &[&str],
|
||||
) -> Result<OAuthTokenResponse> {
|
||||
let audience = get_oauth_audience(&config.oauth_url()?)?;
|
||||
let key_pair = key_pair(1024)?;
|
||||
let certificate = self.sign(config, session_token, &key_pair)?.certificate;
|
||||
let assertion = jwt_utils::create_assertion(&key_pair, &certificate, &audience)?;
|
||||
let parameters = json!({
|
||||
"assertion": assertion,
|
||||
"client_id": config.client_id,
|
||||
"response_type": "token",
|
||||
"scope": scopes.join(" ")
|
||||
});
|
||||
let key = derive_hawk_auth_key_from_session_token(session_token)?;
|
||||
let url = config.authorization_endpoint()?;
|
||||
let request = HawkRequestBuilder::new(Method::Post, url, &key)
|
||||
.body(parameters)
|
||||
.build()?;
|
||||
Self::make_request(request)?.json().map_err(Into::into)
|
||||
}
|
||||
|
||||
fn sign(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &[u8],
|
||||
key_pair: &dyn BrowserIDKeyPair,
|
||||
) -> Result<SignResponse> {
|
||||
let public_key_json = key_pair.to_json(false)?;
|
||||
let parameters = json!({
|
||||
"publicKey": public_key_json,
|
||||
"duration": SIGN_DURATION_MS
|
||||
});
|
||||
let key = derive_hawk_auth_key_from_session_token(session_token)?;
|
||||
let url = config.auth_url_path("v1/certificate/sign")?;
|
||||
let request = HawkRequestBuilder::new(Method::Post, url, &key)
|
||||
.body(parameters)
|
||||
.build()?;
|
||||
Self::make_request(request)?.json().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
fn kw(name: &str) -> Vec<u8> {
|
||||
format!("identity.mozilla.com/picl/v1/{}", name)
|
||||
.as_bytes()
|
||||
.to_vec()
|
||||
}
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
#[allow(dead_code)]
|
||||
fn kwe(name: &str, email: &str) -> Vec<u8> {
|
||||
format!("identity.mozilla.com/picl/v1/{}:{}", name, email)
|
||||
.as_bytes()
|
||||
.to_vec()
|
||||
}
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
pub fn key_pair(len: u32) -> Result<RSABrowserIDKeyPair> {
|
||||
RSABrowserIDKeyPair::generate_random(len)
|
||||
}
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
pub(crate) fn derive_sync_key(kb: &[u8]) -> Result<Vec<u8>> {
|
||||
let salt = [0u8; 0];
|
||||
let context_info = kw("oldsync");
|
||||
derive_hkdf_sha256_key(&kb, &salt, &context_info, KEY_LENGTH * 2)
|
||||
}
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
pub(crate) fn compute_client_state(kb: &[u8]) -> Result<String> {
|
||||
Ok(hex::encode(
|
||||
&digest::digest(&digest::SHA256, &kb)?.as_ref()[0..16],
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
fn get_oauth_audience(oauth_url: &Url) -> Result<String> {
|
||||
let host = oauth_url
|
||||
.host_str()
|
||||
.ok_or_else(|| ErrorKind::AudienceURLWithoutHost)?;
|
||||
match oauth_url.port() {
|
||||
Some(port) => Ok(format!("{}://{}:{}", oauth_url.scheme(), host, port)),
|
||||
None => Ok(format!("{}://{}", oauth_url.scheme(), host)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn derive_hawk_auth_key_from_session_token(session_token: &[u8]) -> Result<Vec<u8>> {
|
||||
let context_info = kw("sessionToken");
|
||||
Ok(derive_hkdf_sha256_key(
|
||||
session_token,
|
||||
&HKDF_SALT,
|
||||
&context_info,
|
||||
KEY_LENGTH * 2,
|
||||
)?)
|
||||
}
|
||||
|
||||
fn derive_hkdf_sha256_key(ikm: &[u8], salt: &[u8], info: &[u8], len: usize) -> Result<Vec<u8>> {
|
||||
let salt = hmac::SigningKey::new(&digest::SHA256, salt);
|
||||
let mut out = vec![0u8; len];
|
||||
hkdf::extract_and_expand(&salt, ikm, info, &mut out)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
#[derive(Deserialize)]
|
||||
pub struct LoginResponse {
|
||||
pub uid: String,
|
||||
#[serde(rename = "sessionToken")]
|
||||
pub session_token: String,
|
||||
pub verified: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
#[derive(Deserialize)]
|
||||
pub struct RecoveryEmailStatusResponse {
|
||||
pub email: String,
|
||||
pub verified: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
#[derive(Deserialize)]
|
||||
pub struct AccountStatusResponse {
|
||||
pub exists: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
#[derive(Deserialize)]
|
||||
pub struct SignResponse {
|
||||
#[serde(rename = "cert")]
|
||||
pub certificate: String,
|
||||
}
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
#[derive(Deserialize)]
|
||||
pub struct KeysResponse {
|
||||
// ka: Vec<u8>,
|
||||
pub wrap_kb: Vec<u8>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ring::{digest, pbkdf2};
|
||||
|
||||
fn quick_strech_pwd(email: &str, pwd: &str) -> Vec<u8> {
|
||||
let salt = kwe("quickStretch", email);
|
||||
let mut out = [0u8; 32];
|
||||
pbkdf2::derive(
|
||||
&digest::SHA256,
|
||||
std::num::NonZeroU32::new(1000).unwrap(),
|
||||
&salt,
|
||||
pwd.as_bytes(),
|
||||
&mut out,
|
||||
);
|
||||
out.to_vec()
|
||||
}
|
||||
|
||||
fn auth_pwd(email: &str, pwd: &str) -> String {
|
||||
let streched = quick_strech_pwd(email, pwd);
|
||||
let salt = [0u8; 0];
|
||||
let context = kw("authPW");
|
||||
let derived = derive_hkdf_sha256_key(&streched, &salt, &context, 32).unwrap();
|
||||
hex::encode(derived)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quick_strech_pwd() {
|
||||
let email = "andré@example.org";
|
||||
let pwd = "pässwörd";
|
||||
let streched = hex::encode(quick_strech_pwd(email, pwd));
|
||||
assert_eq!(
|
||||
streched,
|
||||
"e4e8889bd8bd61ad6de6b95c059d56e7b50dacdaf62bd84644af7e2add84345d"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_auth_pwd() {
|
||||
let email = "andré@example.org";
|
||||
let pwd = "pässwörd";
|
||||
let auth_pwd = auth_pwd(email, pwd);
|
||||
assert_eq!(
|
||||
auth_pwd,
|
||||
"247b675ffb4c46310bc87e26d712153abe5e1c90ef00a4784594f97ef54f2375"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,68 +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/. */
|
||||
|
||||
use crate::error::*;
|
||||
use rc_crypto::hawk::{Credentials, Key, PayloadHasher, RequestBuilder, SHA256};
|
||||
use url::Url;
|
||||
use viaduct::{header_names, Method, Request};
|
||||
|
||||
const KEY_LENGTH: usize = 32;
|
||||
|
||||
pub struct HawkRequestBuilder<'a> {
|
||||
url: Url,
|
||||
method: Method,
|
||||
body: Option<String>,
|
||||
hkdf_sha256_key: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> HawkRequestBuilder<'a> {
|
||||
pub fn new(method: Method, url: Url, hkdf_sha256_key: &'a [u8]) -> Self {
|
||||
rc_crypto::ensure_initialized();
|
||||
HawkRequestBuilder {
|
||||
url,
|
||||
method,
|
||||
body: None,
|
||||
hkdf_sha256_key,
|
||||
}
|
||||
}
|
||||
|
||||
// This class assumes that the content being sent it always of the type
|
||||
// application/json.
|
||||
pub fn body(mut self, body: serde_json::Value) -> Self {
|
||||
self.body = Some(body.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
fn make_hawk_header(&self) -> Result<String> {
|
||||
// Make sure we de-allocate the hash after hawk_request_builder.
|
||||
let hash;
|
||||
let method = format!("{}", self.method);
|
||||
let mut hawk_request_builder = RequestBuilder::from_url(method.as_str(), &self.url)?;
|
||||
if let Some(ref body) = self.body {
|
||||
hash = PayloadHasher::hash("application/json", SHA256, &body)?;
|
||||
hawk_request_builder = hawk_request_builder.hash(&hash[..]);
|
||||
}
|
||||
let hawk_request = hawk_request_builder.request();
|
||||
let token_id = hex::encode(&self.hkdf_sha256_key[0..KEY_LENGTH]);
|
||||
let hmac_key = &self.hkdf_sha256_key[KEY_LENGTH..(2 * KEY_LENGTH)];
|
||||
let hawk_credentials = Credentials {
|
||||
id: token_id,
|
||||
key: Key::new(hmac_key, SHA256)?,
|
||||
};
|
||||
let header = hawk_request.make_header(&hawk_credentials)?;
|
||||
Ok(format!("Hawk {}", header))
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<Request> {
|
||||
let hawk_header = self.make_hawk_header()?;
|
||||
let mut request =
|
||||
Request::new(self.method, self.url).header(header_names::AUTHORIZATION, hawk_header)?;
|
||||
if let Some(body) = self.body {
|
||||
request = request
|
||||
.header(header_names::CONTENT_TYPE, "application/json")?
|
||||
.body(body);
|
||||
}
|
||||
Ok(request)
|
||||
}
|
||||
}
|
|
@ -1,225 +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/. */
|
||||
|
||||
use crate::{error::*, http_client::browser_id::BrowserIDKeyPair};
|
||||
use serde_json::{self, json};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
const DEFAULT_ASSERTION_ISSUER: &str = "127.0.0.1";
|
||||
const DEFAULT_ASSERTION_DURATION: u64 = 60 * 60 * 1000;
|
||||
|
||||
pub fn create_assertion(
|
||||
key_pair: &dyn BrowserIDKeyPair,
|
||||
certificate: &str,
|
||||
audience: &str,
|
||||
) -> Result<String> {
|
||||
let since_epoch = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Something is very wrong.");
|
||||
let issued_at =
|
||||
since_epoch.as_secs() * 1000 + u64::from(since_epoch.subsec_nanos()) / 1_000_000;
|
||||
let expires_at = issued_at + DEFAULT_ASSERTION_DURATION;
|
||||
let issuer = DEFAULT_ASSERTION_ISSUER;
|
||||
create_assertion_full(
|
||||
key_pair,
|
||||
certificate,
|
||||
audience,
|
||||
issuer,
|
||||
issued_at,
|
||||
expires_at,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create_assertion_full(
|
||||
key_pair: &dyn BrowserIDKeyPair,
|
||||
certificate: &str,
|
||||
audience: &str,
|
||||
issuer: &str,
|
||||
issued_at: u64,
|
||||
expires_at: u64,
|
||||
) -> Result<String> {
|
||||
let assertion = SignedJWTBuilder::new(key_pair, issuer, issued_at, expires_at)
|
||||
.audience(&audience)
|
||||
.build()?;
|
||||
Ok(format!("{}~{}", certificate, assertion))
|
||||
}
|
||||
|
||||
struct SignedJWTBuilder<'keypair> {
|
||||
key_pair: &'keypair dyn BrowserIDKeyPair,
|
||||
issuer: String,
|
||||
issued_at: u64,
|
||||
expires_at: u64,
|
||||
audience: Option<String>,
|
||||
payload: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl<'keypair> SignedJWTBuilder<'keypair> {
|
||||
fn new(
|
||||
key_pair: &'keypair dyn BrowserIDKeyPair,
|
||||
issuer: &str,
|
||||
issued_at: u64,
|
||||
expires_at: u64,
|
||||
) -> SignedJWTBuilder<'keypair> {
|
||||
SignedJWTBuilder {
|
||||
key_pair,
|
||||
issuer: issuer.to_owned(),
|
||||
issued_at,
|
||||
expires_at,
|
||||
audience: None,
|
||||
payload: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn audience(mut self, audience: &str) -> SignedJWTBuilder<'keypair> {
|
||||
self.audience = Some(audience.to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn payload(mut self, payload: serde_json::Value) -> SignedJWTBuilder<'keypair> {
|
||||
self.payload = Some(payload);
|
||||
self
|
||||
}
|
||||
|
||||
fn build(self) -> Result<String> {
|
||||
let payload_string = self.get_payload_string()?;
|
||||
encode_and_sign(&payload_string, self.key_pair)
|
||||
}
|
||||
|
||||
fn get_payload_string(&self) -> Result<String> {
|
||||
let mut payload = match self.payload {
|
||||
Some(ref payload) => payload.clone(),
|
||||
None => json!({}),
|
||||
};
|
||||
let obj = match payload.as_object_mut() {
|
||||
Some(obj) => obj,
|
||||
None => panic!("The supplied payload was not an object"),
|
||||
};
|
||||
if let Some(ref audience) = self.audience {
|
||||
obj.insert("aud".to_string(), json!(audience));
|
||||
}
|
||||
obj.insert("iss".to_string(), json!(self.issuer));
|
||||
obj.insert("iat".to_string(), json!(self.issued_at));
|
||||
obj.insert("exp".to_string(), json!(self.expires_at));
|
||||
Ok(json!(obj).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_and_sign(payload: &str, key_pair: &dyn BrowserIDKeyPair) -> Result<String> {
|
||||
let headers_str = json!({"alg": key_pair.get_algo()}).to_string();
|
||||
let encoded_header = base64::encode_config(headers_str.as_bytes(), base64::URL_SAFE_NO_PAD);
|
||||
let encoded_payload = base64::encode_config(payload.as_bytes(), base64::URL_SAFE_NO_PAD);
|
||||
let message = format!("{}.{}", encoded_header, encoded_payload);
|
||||
let signature = key_pair.sign(message.as_bytes())?;
|
||||
let encoded_signature = base64::encode_config(&signature, base64::URL_SAFE_NO_PAD);
|
||||
Ok(format!("{}.{}", message, encoded_signature))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::http_client::browser_id::rsa::RSABrowserIDKeyPair;
|
||||
use crate::http_client::browser_id::BrowserIDKeyPair;
|
||||
|
||||
pub fn create_certificate(
|
||||
serialized_public_key: &serde_json::Value,
|
||||
email: &str,
|
||||
issuer: &str,
|
||||
issued_at: u64,
|
||||
expires_at: u64,
|
||||
key_pair: &dyn BrowserIDKeyPair,
|
||||
) -> Result<String> {
|
||||
let principal = json!({ "email": email });
|
||||
let payload = json!({
|
||||
"principal": principal,
|
||||
"public-key": serialized_public_key
|
||||
});
|
||||
Ok(
|
||||
SignedJWTBuilder::new(key_pair, issuer, issued_at, expires_at)
|
||||
.payload(payload)
|
||||
.build()?,
|
||||
)
|
||||
}
|
||||
|
||||
fn decode(token: &str, key_pair: &dyn BrowserIDKeyPair) -> Result<String> {
|
||||
let segments: Vec<&str> = token.split('.').collect();
|
||||
let message = format!("{}.{}", &segments[0], &segments[1]);
|
||||
let message_bytes = message.as_bytes();
|
||||
let signature = base64::decode_config(&segments[2], base64::URL_SAFE_NO_PAD)?;
|
||||
let verified = key_pair.verify_message(&message_bytes, &signature)?;
|
||||
if !verified {
|
||||
return Err(ErrorKind::JWTSignatureValidationFailed.into());
|
||||
}
|
||||
let payload = base64::decode_config(&segments[1], base64::URL_SAFE_NO_PAD)?;
|
||||
String::from_utf8(payload).map_err(Into::into)
|
||||
}
|
||||
|
||||
// These tests are copied directly from Firefox for Android's TestJSONWebTokenUtils.
|
||||
// They could probably be improved a lot.
|
||||
|
||||
fn do_test_encode_decode(key_pair: &dyn BrowserIDKeyPair) {
|
||||
let payload = json!({"key": "value"}).to_string();
|
||||
|
||||
let token = encode_and_sign(&payload, key_pair).unwrap();
|
||||
let decoded = decode(&token, key_pair).unwrap();
|
||||
assert_eq!(decoded, payload);
|
||||
|
||||
let token_corrupted = format!("{}x", token);
|
||||
assert!(decode(&token_corrupted, key_pair).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rsa_encode_decode() {
|
||||
do_test_encode_decode(&RSABrowserIDKeyPair::generate_random(1024).unwrap());
|
||||
do_test_encode_decode(&RSABrowserIDKeyPair::generate_random(2048).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
// These tests were copied from Firefox for Android (TestJSONWebTokenUtils.java).
|
||||
fn test_rsa_generation() {
|
||||
let mock_modulus = "15498874758090276039465094105837231567265546373975960480941122651107772824121527483107402353899846252489837024870191707394743196399582959425513904762996756672089693541009892030848825079649783086005554442490232900875792851786203948088457942416978976455297428077460890650409549242124655536986141363719589882160081480785048965686285142002320767066674879737238012064156675899512503143225481933864507793118457805792064445502834162315532113963746801770187685650408560424682654937744713813773896962263709692724630650952159596951348264005004375017610441835956073275708740239518011400991972811669493356682993446554779893834303";
|
||||
let mock_public_exponent = "65537";
|
||||
let mock_private_exponent = "6539906961872354450087244036236367269804254381890095841127085551577495913426869112377010004955160417265879626558436936025363204803913318582680951558904318308893730033158178650549970379367915856087364428530828396795995781364659413467784853435450762392157026962694408807947047846891301466649598749901605789115278274397848888140105306063608217776127549926721544215720872305194645129403056801987422794114703255989202755511523434098625000826968430077091984351410839837395828971692109391386427709263149504336916566097901771762648090880994773325283207496645630792248007805177873532441314470502254528486411726581424522838833";
|
||||
|
||||
let n = "20332459213245328760269530796942625317006933400814022542511832260333163206808672913301254872114045771215470352093046136365629411384688395020388553744886954869033696089099714200452682590914843971683468562019706059388121176435204818734091361033445697933682779095713376909412972373727850278295874361806633955236862180792787906413536305117030045164276955491725646610368132167655556353974515423042221261732084368978523747789654468953860772774078384556028728800902433401131226904244661160767916883680495122225202542023841606998867411022088440946301191503335932960267228470933599974787151449279465703844493353175088719018221";
|
||||
let e = "65537";
|
||||
let d = "9362542596354998418106014928820888151984912891492829581578681873633736656469965533631464203894863562319612803232737938923691416707617473868582415657005943574434271946791143554652502483003923911339605326222297167404896789026986450703532494518628015811567189641735787240372075015553947628033216297520493759267733018808392882741098489889488442349031883643894014316243251108104684754879103107764521172490019661792943030921873284592436328217485953770574054344056638447333651425231219150676837203185544359148474983670261712939626697233692596362322419559401320065488125670905499610998631622562652935873085671353890279911361";
|
||||
|
||||
let issuer = "127.0.0.1";
|
||||
let audience = "http://localhost:8080";
|
||||
let iat: u64 = 1_352_995_809_210;
|
||||
let dur: u64 = 60 * 60 * 1000;
|
||||
let exp: u64 = iat + dur;
|
||||
|
||||
let mock_key_pair = RSABrowserIDKeyPair::from_exponents_base10(
|
||||
mock_modulus,
|
||||
mock_public_exponent,
|
||||
mock_private_exponent,
|
||||
)
|
||||
.unwrap();
|
||||
let key_pair_to_sign = RSABrowserIDKeyPair::from_exponents_base10(n, e, d).unwrap();
|
||||
|
||||
let certificate = create_certificate(
|
||||
&key_pair_to_sign.to_json(false).unwrap(),
|
||||
"test@mockmyid.com",
|
||||
"mockmyid.com",
|
||||
iat,
|
||||
exp,
|
||||
&mock_key_pair,
|
||||
)
|
||||
.unwrap();
|
||||
let assertion =
|
||||
create_assertion_full(&key_pair_to_sign, &certificate, audience, issuer, iat, exp)
|
||||
.unwrap();
|
||||
let payload = decode(&certificate, &mock_key_pair).unwrap();
|
||||
let expected_payload = "{\"exp\":1352999409210,\"iat\":1352995809210,\"iss\":\"mockmyid.com\",\"principal\":{\"email\":\"test@mockmyid.com\"},\"public-key\":{\"algorithm\":\"RS\",\"e\":\"65537\",\"n\":\"20332459213245328760269530796942625317006933400814022542511832260333163206808672913301254872114045771215470352093046136365629411384688395020388553744886954869033696089099714200452682590914843971683468562019706059388121176435204818734091361033445697933682779095713376909412972373727850278295874361806633955236862180792787906413536305117030045164276955491725646610368132167655556353974515423042221261732084368978523747789654468953860772774078384556028728800902433401131226904244661160767916883680495122225202542023841606998867411022088440946301191503335932960267228470933599974787151449279465703844493353175088719018221\"}}";
|
||||
assert_eq!(payload, expected_payload);
|
||||
|
||||
let expected_certificate = "eyJhbGciOiJSUzI1NSJ9.eyJleHAiOjEzNTI5OTk0MDkyMTAsImlhdCI6MTM1Mjk5NTgwOTIxMCwiaXNzIjoibW9ja215aWQuY29tIiwicHJpbmNpcGFsIjp7ImVtYWlsIjoidGVzdEBtb2NrbXlpZC5jb20ifSwicHVibGljLWtleSI6eyJhbGdvcml0aG0iOiJSUyIsImUiOiI2NTUzNyIsIm4iOiIyMDMzMjQ1OTIxMzI0NTMyODc2MDI2OTUzMDc5Njk0MjYyNTMxNzAwNjkzMzQwMDgxNDAyMjU0MjUxMTgzMjI2MDMzMzE2MzIwNjgwODY3MjkxMzMwMTI1NDg3MjExNDA0NTc3MTIxNTQ3MDM1MjA5MzA0NjEzNjM2NTYyOTQxMTM4NDY4ODM5NTAyMDM4ODU1Mzc0NDg4Njk1NDg2OTAzMzY5NjA4OTA5OTcxNDIwMDQ1MjY4MjU5MDkxNDg0Mzk3MTY4MzQ2ODU2MjAxOTcwNjA1OTM4ODEyMTE3NjQzNTIwNDgxODczNDA5MTM2MTAzMzQ0NTY5NzkzMzY4Mjc3OTA5NTcxMzM3NjkwOTQxMjk3MjM3MzcyNzg1MDI3ODI5NTg3NDM2MTgwNjYzMzk1NTIzNjg2MjE4MDc5Mjc4NzkwNjQxMzUzNjMwNTExNzAzMDA0NTE2NDI3Njk1NTQ5MTcyNTY0NjYxMDM2ODEzMjE2NzY1NTU1NjM1Mzk3NDUxNTQyMzA0MjIyMTI2MTczMjA4NDM2ODk3ODUyMzc0Nzc4OTY1NDQ2ODk1Mzg2MDc3Mjc3NDA3ODM4NDU1NjAyODcyODgwMDkwMjQzMzQwMTEzMTIyNjkwNDI0NDY2MTE2MDc2NzkxNjg4MzY4MDQ5NTEyMjIyNTIwMjU0MjAyMzg0MTYwNjk5ODg2NzQxMTAyMjA4ODQ0MDk0NjMwMTE5MTUwMzMzNTkzMjk2MDI2NzIyODQ3MDkzMzU5OTk3NDc4NzE1MTQ0OTI3OTQ2NTcwMzg0NDQ5MzM1MzE3NTA4ODcxOTAxODIyMSJ9fQ.a_DXs5LysXoBb6zw3eKVjqIEr8PwXBCqJ0UaLOTNranN18Lw1gAlNDs0wEKvIslvdR3fhWyCm5jRISWTsYlZ8E5XAGwL9LPyFliplxaEVBly-g4mBcZzdDGx37832pwvNHGYnc0qknsjWr0oT8DkZj-ShE3YdVbIlyeGf8191DEJR4aGKccNB2o6itNaa5vrXgMLuZDvXfSDRvE6k2vbQb1wLQQCx_kBwRa6ADmejzVDIqRoKtK7-wCS1zXQzpP3Sa9tOfnKSMHuPkuRTJdrxWHULRkdE0iYmch1YSrGHCtx2kiG09o7YkwH7E53pBSrGcn8mFAdRkNdDrqTdnLV2Q";
|
||||
assert_eq!(certificate, expected_certificate);
|
||||
|
||||
let expected_assertion = "eyJhbGciOiJSUzI1NSJ9.eyJleHAiOjEzNTI5OTk0MDkyMTAsImlhdCI6MTM1Mjk5NTgwOTIxMCwiaXNzIjoibW9ja215aWQuY29tIiwicHJpbmNpcGFsIjp7ImVtYWlsIjoidGVzdEBtb2NrbXlpZC5jb20ifSwicHVibGljLWtleSI6eyJhbGdvcml0aG0iOiJSUyIsImUiOiI2NTUzNyIsIm4iOiIyMDMzMjQ1OTIxMzI0NTMyODc2MDI2OTUzMDc5Njk0MjYyNTMxNzAwNjkzMzQwMDgxNDAyMjU0MjUxMTgzMjI2MDMzMzE2MzIwNjgwODY3MjkxMzMwMTI1NDg3MjExNDA0NTc3MTIxNTQ3MDM1MjA5MzA0NjEzNjM2NTYyOTQxMTM4NDY4ODM5NTAyMDM4ODU1Mzc0NDg4Njk1NDg2OTAzMzY5NjA4OTA5OTcxNDIwMDQ1MjY4MjU5MDkxNDg0Mzk3MTY4MzQ2ODU2MjAxOTcwNjA1OTM4ODEyMTE3NjQzNTIwNDgxODczNDA5MTM2MTAzMzQ0NTY5NzkzMzY4Mjc3OTA5NTcxMzM3NjkwOTQxMjk3MjM3MzcyNzg1MDI3ODI5NTg3NDM2MTgwNjYzMzk1NTIzNjg2MjE4MDc5Mjc4NzkwNjQxMzUzNjMwNTExNzAzMDA0NTE2NDI3Njk1NTQ5MTcyNTY0NjYxMDM2ODEzMjE2NzY1NTU1NjM1Mzk3NDUxNTQyMzA0MjIyMTI2MTczMjA4NDM2ODk3ODUyMzc0Nzc4OTY1NDQ2ODk1Mzg2MDc3Mjc3NDA3ODM4NDU1NjAyODcyODgwMDkwMjQzMzQwMTEzMTIyNjkwNDI0NDY2MTE2MDc2NzkxNjg4MzY4MDQ5NTEyMjIyNTIwMjU0MjAyMzg0MTYwNjk5ODg2NzQxMTAyMjA4ODQ0MDk0NjMwMTE5MTUwMzMzNTkzMjk2MDI2NzIyODQ3MDkzMzU5OTk3NDc4NzE1MTQ0OTI3OTQ2NTcwMzg0NDQ5MzM1MzE3NTA4ODcxOTAxODIyMSJ9fQ.a_DXs5LysXoBb6zw3eKVjqIEr8PwXBCqJ0UaLOTNranN18Lw1gAlNDs0wEKvIslvdR3fhWyCm5jRISWTsYlZ8E5XAGwL9LPyFliplxaEVBly-g4mBcZzdDGx37832pwvNHGYnc0qknsjWr0oT8DkZj-ShE3YdVbIlyeGf8191DEJR4aGKccNB2o6itNaa5vrXgMLuZDvXfSDRvE6k2vbQb1wLQQCx_kBwRa6ADmejzVDIqRoKtK7-wCS1zXQzpP3Sa9tOfnKSMHuPkuRTJdrxWHULRkdE0iYmch1YSrGHCtx2kiG09o7YkwH7E53pBSrGcn8mFAdRkNdDrqTdnLV2Q~eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJleHAiOjEzNTI5OTk0MDkyMTAsImlhdCI6MTM1Mjk5NTgwOTIxMCwiaXNzIjoiMTI3LjAuMC4xIn0.Vi9vl8frqV-devCgV5EEfxyP5omfoWYgehcBMPPBtt-rFgylAUMT48gQb4UQlkRuvdUP7bkfc32KPK6lHCrWNKlsX2O0hnry4lTyFp4g2PGRdCdIGkrQ82hrxWpt-s16x_qW2SkcwcauPYMjOmXkuUnWS5Yx-kjEV07fcy-njl-15NZX8sYFO0uocuRsUXMSp5wibBVbDEEkm9IgRoqBPT9SqnpEwO4RBj0Dx16y4t9eKIvbh_3Jpa3GPUGJWP07t7t2w-622Fmoekcf4Bjfsu-NYtMPj_NE_ZnbZ0VFIv6IdPfPsMHUwwCSy-vFh8ZgvD2EVT1fycT1wTS0Puq-dQ";
|
||||
assert_eq!(assertion, expected_assertion);
|
||||
}
|
||||
}
|
|
@ -1,321 +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/. */
|
||||
|
||||
use super::BrowserIDKeyPair;
|
||||
use crate::error::*;
|
||||
use openssl::{
|
||||
bn::BigNum,
|
||||
hash::MessageDigest,
|
||||
pkey::{PKey, Private},
|
||||
rsa::{Rsa, RsaPrivateKeyBuilder},
|
||||
sign::{Signer, Verifier},
|
||||
};
|
||||
use serde::{
|
||||
de::{self, Deserialize, Deserializer, MapAccess, Visitor},
|
||||
ser::{self, Serialize, SerializeStruct, Serializer},
|
||||
};
|
||||
use serde_json::{self, json};
|
||||
use std::fmt;
|
||||
|
||||
pub struct RSABrowserIDKeyPair {
|
||||
key: PKey<Private>,
|
||||
}
|
||||
|
||||
impl RSABrowserIDKeyPair {
|
||||
fn from_rsa(rsa: Rsa<Private>) -> Result<RSABrowserIDKeyPair> {
|
||||
let key = PKey::from_rsa(rsa)?;
|
||||
Ok(RSABrowserIDKeyPair { key })
|
||||
}
|
||||
|
||||
pub fn generate_random(len: u32) -> Result<RSABrowserIDKeyPair> {
|
||||
let rsa = Rsa::generate(len)?;
|
||||
RSABrowserIDKeyPair::from_rsa(rsa)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn from_exponents_base10(n: &str, e: &str, d: &str) -> Result<RSABrowserIDKeyPair> {
|
||||
let n = BigNum::from_dec_str(n)?;
|
||||
let e = BigNum::from_dec_str(e)?;
|
||||
let d = BigNum::from_dec_str(d)?;
|
||||
let rsa = RsaPrivateKeyBuilder::new(n, e, d)?.build();
|
||||
RSABrowserIDKeyPair::from_rsa(rsa)
|
||||
}
|
||||
}
|
||||
|
||||
impl BrowserIDKeyPair for RSABrowserIDKeyPair {
|
||||
fn get_algo(&self) -> String {
|
||||
format!("RS{}", self.key.bits() / 8)
|
||||
}
|
||||
|
||||
fn sign(&self, message: &[u8]) -> Result<Vec<u8>> {
|
||||
let mut signer = Signer::new(MessageDigest::sha256(), &self.key)?;
|
||||
signer.update(message)?;
|
||||
signer.sign_to_vec().map_err(Into::into)
|
||||
}
|
||||
|
||||
fn verify_message(&self, message: &[u8], signature: &[u8]) -> Result<bool> {
|
||||
let mut verifier = Verifier::new(MessageDigest::sha256(), &self.key)?;
|
||||
verifier.update(message)?;
|
||||
verifier.verify(signature).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn to_json(&self, include_private: bool) -> Result<serde_json::Value> {
|
||||
if include_private {
|
||||
panic!("Not implemented!");
|
||||
}
|
||||
let rsa = self.key.rsa()?;
|
||||
let n = format!("{}", rsa.n().to_dec_str()?);
|
||||
let e = format!("{}", rsa.e().to_dec_str()?);
|
||||
Ok(json!({
|
||||
"algorithm": "RS",
|
||||
"n": n,
|
||||
"e": e
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for RSABrowserIDKeyPair {
|
||||
fn clone(&self) -> RSABrowserIDKeyPair {
|
||||
let rsa = self.key.rsa().unwrap().clone();
|
||||
RSABrowserIDKeyPair::from_rsa(rsa).unwrap() // Yuck
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for RSABrowserIDKeyPair {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "<rsa_key_pair>")
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for RSABrowserIDKeyPair {
|
||||
#[allow(clippy::many_single_char_names)] // FIXME
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_struct("RSABrowserIDKeyPair", 2)?;
|
||||
let rsa = self
|
||||
.key
|
||||
.rsa()
|
||||
.map_err(|err| ser::Error::custom(err.to_string()))?;
|
||||
let n = rsa
|
||||
.n()
|
||||
.to_dec_str()
|
||||
.map_err(|err| ser::Error::custom(err.to_string()))?;
|
||||
let e = rsa
|
||||
.e()
|
||||
.to_dec_str()
|
||||
.map_err(|err| ser::Error::custom(err.to_string()))?;
|
||||
let d = rsa
|
||||
.d()
|
||||
.to_dec_str()
|
||||
.map_err(|err| ser::Error::custom(err.to_string()))?;
|
||||
state.serialize_field("n", &format!("{}", n))?;
|
||||
state.serialize_field("e", &format!("{}", e))?;
|
||||
state.serialize_field("d", &format!("{}", d))?;
|
||||
if let (Some(p), Some(q)) = (rsa.p(), rsa.q()) {
|
||||
let p = p
|
||||
.to_dec_str()
|
||||
.map_err(|err| ser::Error::custom(err.to_string()))?;
|
||||
let q = q
|
||||
.to_dec_str()
|
||||
.map_err(|err| ser::Error::custom(err.to_string()))?;
|
||||
state.serialize_field("p", &format!("{}", p))?;
|
||||
state.serialize_field("q", &format!("{}", q))?;
|
||||
}
|
||||
if let (Some(dmp1), Some(dmq1), Some(iqmp)) = (rsa.dmp1(), rsa.dmq1(), rsa.iqmp()) {
|
||||
let dmp1 = dmp1
|
||||
.to_dec_str()
|
||||
.map_err(|err| ser::Error::custom(err.to_string()))?;
|
||||
let dmq1 = dmq1
|
||||
.to_dec_str()
|
||||
.map_err(|err| ser::Error::custom(err.to_string()))?;
|
||||
let iqmp = iqmp
|
||||
.to_dec_str()
|
||||
.map_err(|err| ser::Error::custom(err.to_string()))?;
|
||||
state.serialize_field("dmp1", &format!("{}", dmp1))?;
|
||||
state.serialize_field("dmq1", &format!("{}", dmq1))?;
|
||||
state.serialize_field("iqmp", &format!("{}", iqmp))?;
|
||||
}
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for RSABrowserIDKeyPair {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
enum Field {
|
||||
N,
|
||||
E,
|
||||
D,
|
||||
P,
|
||||
Q,
|
||||
Dmp1,
|
||||
Dmq1,
|
||||
Iqmp,
|
||||
};
|
||||
|
||||
impl<'de> Deserialize<'de> for Field {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Field, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct FieldVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for FieldVisitor {
|
||||
type Value = Field;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("`n`, `e`, `d`, `p`, `q`, `dmp1`, `dmq1`, `iqmp`")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> std::result::Result<Field, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
match value {
|
||||
"n" => Ok(Field::N),
|
||||
"e" => Ok(Field::E),
|
||||
"d" => Ok(Field::D),
|
||||
"p" => Ok(Field::P),
|
||||
"q" => Ok(Field::Q),
|
||||
"dmp1" => Ok(Field::Dmp1),
|
||||
"dmq1" => Ok(Field::Dmq1),
|
||||
"iqmp" => Ok(Field::Iqmp),
|
||||
_ => Err(de::Error::unknown_field(value, FIELDS)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_identifier(FieldVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct RSABrowserIDKeyPairVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for RSABrowserIDKeyPairVisitor {
|
||||
type Value = RSABrowserIDKeyPair;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("struct RSABrowserIDKeyPair")
|
||||
}
|
||||
#[allow(clippy::many_single_char_names)] // FIXME
|
||||
fn visit_map<V>(self, mut map: V) -> std::result::Result<RSABrowserIDKeyPair, V::Error>
|
||||
where
|
||||
V: MapAccess<'de>,
|
||||
{
|
||||
let mut n = None;
|
||||
let mut e = None;
|
||||
let mut d = None;
|
||||
let mut p = None;
|
||||
let mut q = None;
|
||||
let mut dmp1 = None;
|
||||
let mut dmq1 = None;
|
||||
let mut iqmp = None;
|
||||
while let Some(key) = map.next_key()? {
|
||||
match key {
|
||||
Field::N => {
|
||||
if n.is_some() {
|
||||
return Err(de::Error::duplicate_field("n"));
|
||||
}
|
||||
n = Some(map.next_value()?);
|
||||
}
|
||||
Field::E => {
|
||||
if e.is_some() {
|
||||
return Err(de::Error::duplicate_field("e"));
|
||||
}
|
||||
e = Some(map.next_value()?);
|
||||
}
|
||||
Field::D => {
|
||||
if d.is_some() {
|
||||
return Err(de::Error::duplicate_field("d"));
|
||||
}
|
||||
d = Some(map.next_value()?);
|
||||
}
|
||||
Field::P => {
|
||||
if p.is_some() {
|
||||
return Err(de::Error::duplicate_field("p"));
|
||||
}
|
||||
p = Some(map.next_value()?);
|
||||
}
|
||||
Field::Q => {
|
||||
if q.is_some() {
|
||||
return Err(de::Error::duplicate_field("q"));
|
||||
}
|
||||
q = Some(map.next_value()?);
|
||||
}
|
||||
Field::Dmp1 => {
|
||||
if dmp1.is_some() {
|
||||
return Err(de::Error::duplicate_field("dmp1"));
|
||||
}
|
||||
dmp1 = Some(map.next_value()?);
|
||||
}
|
||||
Field::Dmq1 => {
|
||||
if dmq1.is_some() {
|
||||
return Err(de::Error::duplicate_field("dmq1"));
|
||||
}
|
||||
dmq1 = Some(map.next_value()?);
|
||||
}
|
||||
Field::Iqmp => {
|
||||
if iqmp.is_some() {
|
||||
return Err(de::Error::duplicate_field("iqmp"));
|
||||
}
|
||||
iqmp = Some(map.next_value()?);
|
||||
}
|
||||
}
|
||||
}
|
||||
let n = n.ok_or_else(|| de::Error::missing_field("n"))?;
|
||||
let n =
|
||||
BigNum::from_dec_str(n).map_err(|err| de::Error::custom(err.to_string()))?;
|
||||
let e = e.ok_or_else(|| de::Error::missing_field("e"))?;
|
||||
let e =
|
||||
BigNum::from_dec_str(e).map_err(|err| de::Error::custom(err.to_string()))?;
|
||||
let d = d.ok_or_else(|| de::Error::missing_field("d"))?;
|
||||
let d =
|
||||
BigNum::from_dec_str(d).map_err(|err| de::Error::custom(err.to_string()))?;
|
||||
let mut builder = RsaPrivateKeyBuilder::new(n, e, d)
|
||||
.map_err(|err| de::Error::custom(err.to_string()))?;
|
||||
if let (Some(p), Some(q)) = (p, q) {
|
||||
let p = BigNum::from_dec_str(p)
|
||||
.map_err(|err| de::Error::custom(err.to_string()))?;
|
||||
let q = BigNum::from_dec_str(q)
|
||||
.map_err(|err| de::Error::custom(err.to_string()))?;
|
||||
builder = builder
|
||||
.set_factors(p, q)
|
||||
.map_err(|err| de::Error::custom(err.to_string()))?;
|
||||
}
|
||||
if let (Some(dmp1), Some(dmq1), Some(iqmp)) = (dmp1, dmq1, iqmp) {
|
||||
let dmp1 = BigNum::from_dec_str(dmp1)
|
||||
.map_err(|err| de::Error::custom(err.to_string()))?;
|
||||
let dmq1 = BigNum::from_dec_str(dmq1)
|
||||
.map_err(|err| de::Error::custom(err.to_string()))?;
|
||||
let iqmp = BigNum::from_dec_str(iqmp)
|
||||
.map_err(|err| de::Error::custom(err.to_string()))?;
|
||||
builder = builder
|
||||
.set_crt_params(dmp1, dmq1, iqmp)
|
||||
.map_err(|err| de::Error::custom(err.to_string()))?;
|
||||
}
|
||||
let rsa = builder.build();
|
||||
RSABrowserIDKeyPair::from_rsa(rsa).map_err(|err| de::Error::custom(err.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
const FIELDS: &[&str] = &["n", "e", "d", "p", "q", "dmp1", "dmq1", "iqmp"];
|
||||
deserializer.deserialize_struct("RSABrowserIDKeyPair", FIELDS, RSABrowserIDKeyPairVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_serialize_deserialize() {
|
||||
let key_pair = RSABrowserIDKeyPair::generate_random(2048).unwrap();
|
||||
let as_json = serde_json::to_string(&key_pair).unwrap();
|
||||
let _key_pair: RSABrowserIDKeyPair = serde_json::from_str(&as_json).unwrap();
|
||||
}
|
||||
}
|
|
@ -5,10 +5,6 @@
|
|||
#![allow(unknown_lints)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
pub use crate::browser_id::{SyncKeys, WebChannelResponse};
|
||||
#[cfg(feature = "browserid")]
|
||||
use crate::login_sm::LoginState;
|
||||
use crate::{
|
||||
commands::send_tab::SendTabPayload,
|
||||
device::{Capability as DeviceCapability, Device},
|
||||
|
@ -24,8 +20,6 @@ use std::{
|
|||
};
|
||||
use url::Url;
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
mod browser_id;
|
||||
mod commands;
|
||||
mod config;
|
||||
pub mod device;
|
||||
|
@ -37,8 +31,6 @@ pub mod msg_types {
|
|||
include!(concat!(env!("OUT_DIR"), "/msg_types.rs"));
|
||||
}
|
||||
mod http_client;
|
||||
#[cfg(feature = "browserid")]
|
||||
mod login_sm;
|
||||
mod oauth;
|
||||
mod profile;
|
||||
mod scoped_keys;
|
||||
|
@ -47,9 +39,6 @@ pub mod send_tab;
|
|||
mod state_persistence;
|
||||
mod util;
|
||||
|
||||
#[cfg(feature = "browserid")]
|
||||
type FxAClient = dyn http_client::browser_id::FxABrowserIDClient + Sync + Send;
|
||||
#[cfg(not(feature = "browserid"))]
|
||||
type FxAClient = dyn http_client::FxAClient + Sync + Send;
|
||||
|
||||
pub struct FirefoxAccount {
|
||||
|
@ -67,8 +56,6 @@ pub struct FirefoxAccount {
|
|||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct StateV2 {
|
||||
config: Config,
|
||||
#[cfg(feature = "browserid")]
|
||||
login_state: LoginState,
|
||||
refresh_token: Option<RefreshToken>,
|
||||
scoped_keys: HashMap<String, ScopedKey>,
|
||||
last_handled_command: Option<u64>,
|
||||
|
@ -97,8 +84,6 @@ impl FirefoxAccount {
|
|||
pub fn with_config(config: Config) -> Self {
|
||||
Self::from_state(StateV2 {
|
||||
config,
|
||||
#[cfg(feature = "browserid")]
|
||||
login_state: LoginState::Unknown,
|
||||
refresh_token: None,
|
||||
scoped_keys: HashMap::new(),
|
||||
last_handled_command: None,
|
||||
|
|
|
@ -1,294 +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/. */
|
||||
|
||||
use crate::{
|
||||
error::*,
|
||||
http_client::{self, browser_id::rsa::RSABrowserIDKeyPair, *},
|
||||
util::{now, Xorable},
|
||||
Config,
|
||||
};
|
||||
use serde_derive::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct LoginStateMachine<'a> {
|
||||
config: &'a Config,
|
||||
client: Arc<dyn http_client::browser_id::FxABrowserIDClient>,
|
||||
}
|
||||
|
||||
impl<'a> LoginStateMachine<'a> {
|
||||
pub fn new(
|
||||
config: &'a Config,
|
||||
client: Arc<dyn http_client::browser_id::FxABrowserIDClient>,
|
||||
) -> LoginStateMachine<'_> {
|
||||
LoginStateMachine { config, client }
|
||||
}
|
||||
|
||||
pub fn advance(&self, from: LoginState) -> Result<LoginState> {
|
||||
let mut cur_state = from;
|
||||
loop {
|
||||
let cur_state_discriminant = std::mem::discriminant(&cur_state);
|
||||
let new_state = self.advance_one(cur_state)?;
|
||||
let new_state_discriminant = std::mem::discriminant(&new_state);
|
||||
cur_state = new_state;
|
||||
if cur_state_discriminant == new_state_discriminant {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(cur_state)
|
||||
}
|
||||
|
||||
fn advance_one(&self, from: LoginState) -> Result<LoginState> {
|
||||
log::info!("advancing from state {:?}", from);
|
||||
match from {
|
||||
LoginState::Married(state) => {
|
||||
let now = now();
|
||||
log::debug!("Checking key pair and certificate freshness.");
|
||||
if now > state.token_keys_and_key_pair.key_pair_expires_at {
|
||||
log::info!("Key pair has expired. Transitioning to CohabitingBeforeKeyPair.");
|
||||
Ok(LoginState::CohabitingBeforeKeyPair(
|
||||
state.token_keys_and_key_pair.token_and_keys,
|
||||
))
|
||||
} else if now > state.certificate_expires_at {
|
||||
log::info!("Certificate has expired. Transitioning to CohabitingAfterKeyPair.");
|
||||
Ok(LoginState::CohabitingAfterKeyPair(
|
||||
state.token_keys_and_key_pair,
|
||||
))
|
||||
} else {
|
||||
log::info!("Key pair and certificate are fresh; staying Married.");
|
||||
Ok(LoginState::Married(state)) // same
|
||||
}
|
||||
}
|
||||
LoginState::CohabitingBeforeKeyPair(state) => {
|
||||
log::debug!("Generating key pair.");
|
||||
let key_pair = match browser_id::key_pair(2048) {
|
||||
Ok(key_pair) => key_pair,
|
||||
Err(_) => {
|
||||
log::error!("Failed to generate key pair! Transitioning to Separated.");
|
||||
return Ok(LoginState::Separated(state.base));
|
||||
}
|
||||
};
|
||||
log::info!("Key pair generated! Transitioning to CohabitingAfterKeyPairState.");
|
||||
let new_state = CohabitingAfterKeyPairState {
|
||||
token_and_keys: state,
|
||||
key_pair,
|
||||
key_pair_expires_at: now() + 30 * 24 * 3600 * 1000,
|
||||
};
|
||||
Ok(LoginState::CohabitingAfterKeyPair(new_state))
|
||||
}
|
||||
LoginState::CohabitingAfterKeyPair(state) => {
|
||||
log::debug!("Signing public key.");
|
||||
let resp = self.client.sign(
|
||||
&self.config,
|
||||
&state.token_and_keys.session_token,
|
||||
&state.key_pair,
|
||||
);
|
||||
match resp {
|
||||
Ok(resp) => {
|
||||
log::info!("Signed public key! Transitioning to Married.");
|
||||
let new_state = MarriedState {
|
||||
token_keys_and_key_pair: state,
|
||||
certificate: resp.certificate,
|
||||
certificate_expires_at: now() + 24 * 3600 * 1000,
|
||||
};
|
||||
Ok(LoginState::Married(new_state))
|
||||
}
|
||||
Err(e) => {
|
||||
if let ErrorKind::RemoteError { .. } = e.kind() {
|
||||
log::error!("Server error: {:?}. Transitioning to Separated.", e);
|
||||
Ok(LoginState::Separated(state.token_and_keys.base))
|
||||
} else {
|
||||
log::error!(
|
||||
"Unknown error: ({:?}). Assuming transient, not transitioning.",
|
||||
e
|
||||
);
|
||||
Ok(LoginState::CohabitingAfterKeyPair(state))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LoginState::EngagedBeforeVerified(state) => {
|
||||
self.handle_ready_for_key_state(LoginState::EngagedBeforeVerified, state)
|
||||
}
|
||||
LoginState::EngagedAfterVerified(state) => {
|
||||
self.handle_ready_for_key_state(LoginState::EngagedAfterVerified, state)
|
||||
}
|
||||
LoginState::Separated(_) => Ok(from),
|
||||
LoginState::Unknown => Ok(from),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_ready_for_key_state<F: FnOnce(ReadyForKeysState) -> LoginState>(
|
||||
&self,
|
||||
same: F,
|
||||
state: ReadyForKeysState,
|
||||
) -> Result<LoginState> {
|
||||
log::debug!("Fetching keys.");
|
||||
let resp = self.client.keys(&self.config, &state.key_fetch_token);
|
||||
match resp {
|
||||
Ok(resp) => {
|
||||
let kb = match resp.wrap_kb.xored_with(&state.unwrap_kb) {
|
||||
Ok(kb) => kb,
|
||||
Err(_) => {
|
||||
log::error!("Failed to unwrap keys response! Transitioning to Separated.");
|
||||
return Ok(same(state));
|
||||
}
|
||||
};
|
||||
log::info!("Unwrapped keys response. Transition to CohabitingBeforeKeyPair.");
|
||||
let sync_key = browser_id::derive_sync_key(&kb)?;
|
||||
let xcs = browser_id::compute_client_state(&kb)?;
|
||||
Ok(LoginState::CohabitingBeforeKeyPair(TokenAndKeysState {
|
||||
base: state.base,
|
||||
session_token: state.session_token.to_vec(),
|
||||
sync_key,
|
||||
xcs,
|
||||
}))
|
||||
}
|
||||
Err(e) => match e.kind() {
|
||||
ErrorKind::RemoteError { errno: 104, .. } => {
|
||||
log::warn!("Account not yet verified, not transitioning.");
|
||||
Ok(same(state))
|
||||
}
|
||||
ErrorKind::RemoteError { .. } => {
|
||||
log::error!("Server error: {:?}. Transitioning to Separated.", e);
|
||||
Ok(LoginState::Separated(state.base))
|
||||
}
|
||||
_ => {
|
||||
log::error!(
|
||||
"Unknown error: ({:?}). Assuming transient, not transitioning.",
|
||||
e
|
||||
);
|
||||
Ok(same(state))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub enum LoginState {
|
||||
Married(MarriedState),
|
||||
CohabitingBeforeKeyPair(CohabitingBeforeKeyPairState),
|
||||
CohabitingAfterKeyPair(CohabitingAfterKeyPairState),
|
||||
EngagedBeforeVerified(EngagedBeforeVerifiedState),
|
||||
EngagedAfterVerified(EngagedAfterVerifiedState),
|
||||
Separated(SeparatedState),
|
||||
Unknown, // If a client never uses the session_token flows, we will be in this state.
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct MarriedState {
|
||||
token_keys_and_key_pair: TokenKeysAndKeyPairState,
|
||||
certificate: String,
|
||||
certificate_expires_at: u64,
|
||||
}
|
||||
|
||||
pub type CohabitingBeforeKeyPairState = TokenAndKeysState;
|
||||
pub type CohabitingAfterKeyPairState = TokenKeysAndKeyPairState;
|
||||
pub type EngagedBeforeVerifiedState = ReadyForKeysState;
|
||||
pub type EngagedAfterVerifiedState = ReadyForKeysState;
|
||||
pub type SeparatedState = BaseState;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct ReadyForKeysState {
|
||||
base: BaseState,
|
||||
session_token: Vec<u8>,
|
||||
key_fetch_token: Vec<u8>,
|
||||
unwrap_kb: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct TokenAndKeysState {
|
||||
base: BaseState,
|
||||
session_token: Vec<u8>,
|
||||
sync_key: Vec<u8>,
|
||||
xcs: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct TokenKeysAndKeyPairState {
|
||||
token_and_keys: TokenAndKeysState,
|
||||
key_pair: RSABrowserIDKeyPair,
|
||||
key_pair_expires_at: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct BaseState {
|
||||
uid: String,
|
||||
email: String,
|
||||
}
|
||||
|
||||
impl ReadyForKeysState {
|
||||
pub fn new(
|
||||
uid: String,
|
||||
email: String,
|
||||
session_token: Vec<u8>,
|
||||
key_fetch_token: Vec<u8>,
|
||||
unwrap_kb: Vec<u8>,
|
||||
) -> ReadyForKeysState {
|
||||
ReadyForKeysState {
|
||||
base: BaseState { uid, email },
|
||||
session_token,
|
||||
key_fetch_token,
|
||||
unwrap_kb,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SessionTokenState {
|
||||
fn session_token(&self) -> &[u8];
|
||||
}
|
||||
|
||||
impl SessionTokenState for ReadyForKeysState {
|
||||
fn session_token(&self) -> &[u8] {
|
||||
&self.session_token
|
||||
}
|
||||
}
|
||||
|
||||
impl SessionTokenState for TokenAndKeysState {
|
||||
fn session_token(&self) -> &[u8] {
|
||||
&self.session_token
|
||||
}
|
||||
}
|
||||
|
||||
impl SessionTokenState for TokenKeysAndKeyPairState {
|
||||
fn session_token(&self) -> &[u8] {
|
||||
self.token_and_keys.session_token()
|
||||
}
|
||||
}
|
||||
|
||||
impl SessionTokenState for MarriedState {
|
||||
fn session_token(&self) -> &[u8] {
|
||||
self.token_keys_and_key_pair.session_token()
|
||||
}
|
||||
}
|
||||
|
||||
impl MarriedState {
|
||||
pub fn key_pair(&self) -> &RSABrowserIDKeyPair {
|
||||
&self.token_keys_and_key_pair.key_pair
|
||||
}
|
||||
pub fn certificate(&self) -> &str {
|
||||
&self.certificate
|
||||
}
|
||||
pub fn sync_key(&self) -> &[u8] {
|
||||
&self.token_keys_and_key_pair.token_and_keys.sync_key
|
||||
}
|
||||
pub fn xcs(&self) -> &str {
|
||||
&self.token_keys_and_key_pair.token_and_keys.xcs
|
||||
}
|
||||
}
|
||||
|
||||
impl LoginState {
|
||||
pub fn into_separated(self) -> Self {
|
||||
use self::LoginState::*;
|
||||
match self {
|
||||
Married(state) => Separated(state.token_keys_and_key_pair.token_and_keys.base),
|
||||
CohabitingBeforeKeyPair(state) => Separated(state.base),
|
||||
CohabitingAfterKeyPair(state) => Separated(state.token_and_keys.base),
|
||||
EngagedBeforeVerified(state) => Separated(state.base),
|
||||
EngagedAfterVerified(state) => Separated(state.base),
|
||||
Separated(state) => Separated(state),
|
||||
Unknown => panic!("Insane state."),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,17 +25,15 @@ impl FirefoxAccount {
|
|||
return Err(ErrorKind::IllegalState("Session Token is already set.").into());
|
||||
}
|
||||
// Trade our session token for a refresh token.
|
||||
self.state.session_token = Some(session_token.to_string());
|
||||
let session_token = hex::decode(&session_token)?;
|
||||
let duplicate_session = self
|
||||
.client
|
||||
.duplicate_session(&self.state.config, &session_token)?;
|
||||
|
||||
let duplicated_session_token = duplicate_session.session_token;
|
||||
let duplicated_session_token_bytes = hex::decode(duplicated_session_token)?;
|
||||
let oauth_response = self.client.oauth_token_from_session_token(
|
||||
self.state.session_token = Some(duplicated_session_token.clone());
|
||||
let oauth_response = self.client.oauth_tokens_from_session_token(
|
||||
&self.state.config,
|
||||
&duplicated_session_token_bytes,
|
||||
&duplicated_session_token,
|
||||
&[scopes::PROFILE, scopes::OLD_SYNC],
|
||||
)?;
|
||||
self.handle_oauth_response(oauth_response, None)?;
|
||||
|
@ -45,9 +43,11 @@ impl FirefoxAccount {
|
|||
let k_sync = base64::encode_config(&k_sync, base64::URL_SAFE_NO_PAD);
|
||||
let k_xcs = hex::decode(k_xcs)?;
|
||||
let k_xcs = base64::encode_config(&k_xcs, base64::URL_SAFE_NO_PAD);
|
||||
let scoped_key_data =
|
||||
self.client
|
||||
.scoped_key_data(&self.state.config, &session_token, scopes::OLD_SYNC)?;
|
||||
let scoped_key_data = self.client.scoped_key_data(
|
||||
&self.state.config,
|
||||
&duplicated_session_token,
|
||||
scopes::OLD_SYNC,
|
||||
)?;
|
||||
let oldsync_key_data = scoped_key_data.get(scopes::OLD_SYNC).ok_or_else(|| {
|
||||
ErrorKind::IllegalState("The session token doesn't have access to kSync!")
|
||||
})?;
|
||||
|
|
|
@ -49,23 +49,14 @@ impl FirefoxAccount {
|
|||
return Err(ErrorKind::NoCachedToken(scope.to_string()).into());
|
||||
}
|
||||
}
|
||||
None => {
|
||||
#[cfg(feature = "browserid")]
|
||||
{
|
||||
match Self::session_token_from_state(&self.state.login_state) {
|
||||
Some(session_token) => self.client.oauth_token_with_session_token(
|
||||
&self.state.config,
|
||||
session_token,
|
||||
&[scope],
|
||||
)?,
|
||||
None => return Err(ErrorKind::NoCachedToken(scope.to_string()).into()),
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "browserid"))]
|
||||
{
|
||||
return Err(ErrorKind::NoCachedToken(scope.to_string()).into());
|
||||
}
|
||||
}
|
||||
None => match self.state.session_token {
|
||||
Some(ref session_token) => self.client.oauth_token_with_session_token(
|
||||
&self.state.config,
|
||||
&session_token,
|
||||
&[scope],
|
||||
)?,
|
||||
None => return Err(ErrorKind::NoCachedToken(scope.to_string()).into()),
|
||||
},
|
||||
};
|
||||
let since_epoch = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
|
@ -167,7 +158,7 @@ impl FirefoxAccount {
|
|||
Some(oauth_flow) => oauth_flow,
|
||||
None => return Err(ErrorKind::UnknownOAuthState.into()),
|
||||
};
|
||||
let resp = self.client.oauth_token_with_code(
|
||||
let resp = self.client.oauth_tokens_from_code(
|
||||
&self.state.config,
|
||||
&code,
|
||||
&oauth_flow.code_verifier,
|
||||
|
|
|
@ -77,7 +77,6 @@ impl FirefoxAccount {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "browserid"))] // Otherwise gotta impl FxABrowserIDClient too...
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -81,8 +81,6 @@ impl From<StateV1> for Result<StateV2> {
|
|||
state.client_id,
|
||||
state.redirect_uri,
|
||||
),
|
||||
#[cfg(feature = "browserid")]
|
||||
login_state: super::login_sm::LoginState::Unknown,
|
||||
refresh_token,
|
||||
scoped_keys: all_scoped_keys,
|
||||
last_handled_command: None,
|
||||
|
@ -99,8 +97,6 @@ struct StateV1 {
|
|||
client_id: String,
|
||||
redirect_uri: String,
|
||||
config: V1Config,
|
||||
// #[cfg(feature = "browserid")]
|
||||
// login_state: LoginState, // Wasn't used anyway
|
||||
oauth_cache: HashMap<String, V1AuthInfo>,
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ sync15 = { path = "../sync15" }
|
|||
serde = "1.0.94"
|
||||
serde_derive = "1.0.94"
|
||||
serde_json = "1.0.40"
|
||||
log = "0.4.6"
|
||||
log = "0.4.7"
|
||||
lazy_static = "1.1.0"
|
||||
url = "1.7.1"
|
||||
failure = "0.1.3"
|
||||
|
|
|
@ -45,6 +45,9 @@ configurations {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
// Part of the public API.
|
||||
api project(':sync15-library')
|
||||
|
||||
jnaForTest "net.java.dev.jna:jna:$jna_version@jar"
|
||||
implementation "net.java.dev.jna:jna:$jna_version@aar"
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ package mozilla.appservices.logins
|
|||
import com.sun.jna.Pointer
|
||||
import mozilla.appservices.logins.rust.PasswordSyncAdapter
|
||||
import mozilla.appservices.logins.rust.RustError
|
||||
import mozilla.appservices.sync15.SyncTelemetryPing
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
/**
|
||||
|
@ -92,8 +93,8 @@ class DatabaseLoginsStorage(private val dbPath: String) : AutoCloseable, LoginsS
|
|||
}
|
||||
|
||||
@Throws(LoginsStorageException::class)
|
||||
override fun sync(syncInfo: SyncUnlockInfo) {
|
||||
rustCallWithLock { raw, error ->
|
||||
override fun sync(syncInfo: SyncUnlockInfo): SyncTelemetryPing {
|
||||
val json = rustCallWithLock { raw, error ->
|
||||
PasswordSyncAdapter.INSTANCE.sync15_passwords_sync(
|
||||
raw,
|
||||
syncInfo.kid,
|
||||
|
@ -101,8 +102,9 @@ class DatabaseLoginsStorage(private val dbPath: String) : AutoCloseable, LoginsS
|
|||
syncInfo.syncKey,
|
||||
syncInfo.tokenserverURL,
|
||||
error
|
||||
)
|
||||
)?.getAndConsumeRustString()
|
||||
}
|
||||
return SyncTelemetryPing.fromJSONString(json)
|
||||
}
|
||||
|
||||
@Throws(LoginsStorageException::class)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.logins
|
||||
import mozilla.appservices.sync15.SyncTelemetryPing
|
||||
|
||||
class SyncUnlockInfo(
|
||||
val kid: String,
|
||||
|
@ -81,7 +82,7 @@ interface LoginsStorage : AutoCloseable {
|
|||
* @throws [LoginsStorageException] On unexpected errors (IO failure, rust panics, etc)
|
||||
*/
|
||||
@Throws(LoginsStorageException::class)
|
||||
fun sync(syncInfo: SyncUnlockInfo)
|
||||
fun sync(syncInfo: SyncUnlockInfo): SyncTelemetryPing
|
||||
|
||||
/**
|
||||
* Delete all locally stored login sync metadata (last sync timestamps, etc).
|
||||
|
|
|
@ -6,6 +6,7 @@ package mozilla.appservices.logins
|
|||
|
||||
import android.util.Log
|
||||
import java.util.UUID
|
||||
import mozilla.appservices.sync15.SyncTelemetryPing
|
||||
|
||||
private enum class LoginsStorageState {
|
||||
Unlocked,
|
||||
|
@ -88,9 +89,10 @@ class MemoryLoginsStorage(private var list: List<ServerPassword>) : AutoCloseabl
|
|||
|
||||
@Synchronized
|
||||
@Throws(LoginsStorageException::class)
|
||||
override fun sync(syncInfo: SyncUnlockInfo) {
|
||||
override fun sync(syncInfo: SyncUnlockInfo): SyncTelemetryPing {
|
||||
checkUnlocked()
|
||||
Log.w("MemoryLoginsStorage", "Not syncing because this implementation can not sync")
|
||||
return SyncTelemetryPing(version = 1, uid = "uid", events = emptyList(), syncs = emptyList())
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
|
|
|
@ -43,6 +43,7 @@ internal interface PasswordSyncAdapter : Library {
|
|||
// return json array
|
||||
fun sync15_passwords_get_all(handle: LoginsDbHandle, error: RustError.ByReference): Pointer?
|
||||
|
||||
// Returns a JSON string containing a sync ping.
|
||||
fun sync15_passwords_sync(
|
||||
handle: LoginsDbHandle,
|
||||
key_id: String,
|
||||
|
@ -50,7 +51,7 @@ internal interface PasswordSyncAdapter : Library {
|
|||
sync_key: String,
|
||||
token_server_url: String,
|
||||
error: RustError.ByReference
|
||||
)
|
||||
): Pointer?
|
||||
|
||||
fun sync15_passwords_wipe(handle: LoginsDbHandle, error: RustError.ByReference)
|
||||
fun sync15_passwords_wipe_local(handle: LoginsDbHandle, error: RustError.ByReference)
|
||||
|
|
|
@ -95,10 +95,10 @@ pub extern "C" fn sync15_passwords_sync(
|
|||
sync_key: FfiStr<'_>,
|
||||
tokenserver_url: FfiStr<'_>,
|
||||
error: &mut ExternError,
|
||||
) {
|
||||
) -> *mut c_char {
|
||||
log::debug!("sync15_passwords_sync");
|
||||
ENGINES.call_with_result(error, handle, |state| -> Result<()> {
|
||||
state.sync(
|
||||
ENGINES.call_with_result(error, handle, |state| -> Result<_> {
|
||||
let ping = state.sync(
|
||||
&sync15::Sync15StorageClientInit {
|
||||
key_id: key_id.into_string(),
|
||||
access_token: access_token.into_string(),
|
||||
|
@ -106,7 +106,7 @@ pub extern "C" fn sync15_passwords_sync(
|
|||
},
|
||||
&sync15::KeyBundle::from_ksync_base64(sync_key.as_str())?,
|
||||
)?;
|
||||
Ok(())
|
||||
Ok(ping)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -136,11 +136,12 @@ open class LoginsStorage {
|
|||
close()
|
||||
}
|
||||
|
||||
/// Synchronize with the server.
|
||||
open func sync(unlockInfo: SyncUnlockInfo) throws {
|
||||
try queue.sync {
|
||||
/// Synchronize with the server. Returns the sync telemetry "ping" as a JSON
|
||||
/// string.
|
||||
open func sync(unlockInfo: SyncUnlockInfo) throws -> String {
|
||||
return try queue.sync {
|
||||
let engine = try self.getUnlocked()
|
||||
try LoginsStoreError.unwrap { err in
|
||||
let ptr = try LoginsStoreError.unwrap { err in
|
||||
sync15_passwords_sync(engine,
|
||||
unlockInfo.kid,
|
||||
unlockInfo.fxaAccessToken,
|
||||
|
@ -148,6 +149,7 @@ open class LoginsStorage {
|
|||
unlockInfo.tokenserverURL,
|
||||
err)
|
||||
}
|
||||
return String(freeingRustString: ptr)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -52,12 +52,12 @@ char *_Nullable sync15_passwords_get_by_id(Sync15PasswordEngineHandle handle,
|
|||
char *_Nullable sync15_passwords_get_all(Sync15PasswordEngineHandle handle,
|
||||
Sync15PasswordsError *_Nonnull error_out);
|
||||
|
||||
void sync15_passwords_sync(Sync15PasswordEngineHandle handle,
|
||||
char const *_Nonnull key_id,
|
||||
char const *_Nonnull access_token,
|
||||
char const *_Nonnull sync_key,
|
||||
char const *_Nonnull token_server_url,
|
||||
Sync15PasswordsError *_Nonnull error);
|
||||
char *_Nullable sync15_passwords_sync(Sync15PasswordEngineHandle handle,
|
||||
char const *_Nonnull key_id,
|
||||
char const *_Nonnull access_token,
|
||||
char const *_Nonnull sync_key,
|
||||
char const *_Nonnull token_server_url,
|
||||
Sync15PasswordsError *_Nonnull error);
|
||||
|
||||
void sync15_passwords_wipe(Sync15PasswordEngineHandle handle,
|
||||
Sync15PasswordsError *_Nonnull error);
|
||||
|
|
|
@ -117,6 +117,9 @@ impl PasswordEngine {
|
|||
self.db.set_global_state(&disk_cached_state)?;
|
||||
|
||||
// for b/w compat reasons, we do some dances with the result.
|
||||
// XXX - note that this means telemetry isn't going to be reported back
|
||||
// to the app - we need to check with lockwise about whether they really
|
||||
// need these failures to be reported or whether we can loosen this.
|
||||
if let Err(e) = result.result {
|
||||
return Err(e.into());
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ bytes = "0.4.11"
|
|||
dogear = "0.2.6"
|
||||
interrupt = { path = "../support/interrupt" }
|
||||
error-support = { path = "../support/error" }
|
||||
sync-guid = { path = "../support/guid", features = ["rusqlite_support", "random"]}
|
||||
|
||||
|
||||
[dependencies.rusqlite]
|
||||
version = "0.19.0"
|
||||
|
|
|
@ -12,8 +12,9 @@ use places::storage::bookmarks::{
|
|||
fetch_tree, insert_tree, BookmarkNode, BookmarkRootGuid, BookmarkTreeNode, FetchDepth,
|
||||
FolderNode, SeparatorNode,
|
||||
};
|
||||
use places::types::{BookmarkType, SyncGuid, Timestamp};
|
||||
use places::types::{BookmarkType, Timestamp};
|
||||
use places::{ConnectionType, PlacesApi, PlacesDb};
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
|
||||
use failure::Fail;
|
||||
use serde_derive::*;
|
||||
|
|
|
@ -24,6 +24,7 @@ bytes = "0.4.12"
|
|||
viaduct = { path = "../../viaduct" }
|
||||
interrupt = { path = "../../support/interrupt" }
|
||||
sql-support = { path = "../../support/sql" }
|
||||
sync-guid = { path = "../../support/guid" }
|
||||
|
||||
[dependencies.sync15]
|
||||
path = "../../sync15"
|
||||
|
|
|
@ -15,11 +15,12 @@ use ffi_support::{
|
|||
use places::error::*;
|
||||
use places::msg_types::BookmarkNodeList;
|
||||
use places::storage::bookmarks;
|
||||
use places::types::{SyncGuid, VisitTransitionSet};
|
||||
use places::types::VisitTransitionSet;
|
||||
use places::{storage, ConnectionType, PlacesApi, PlacesDb};
|
||||
use sql_support::SqlInterruptHandle;
|
||||
use std::os::raw::c_char;
|
||||
use std::sync::Arc;
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
|
||||
use places::api::matcher::{self, match_url, search_frecent, SearchParams};
|
||||
|
||||
|
@ -467,7 +468,7 @@ pub extern "C" fn bookmarks_get_tree(
|
|||
) -> ByteBuffer {
|
||||
log::debug!("bookmarks_get_tree");
|
||||
CONNECTIONS.call_with_result(error, handle, |conn| -> places::Result<_> {
|
||||
let root_id = SyncGuid(guid.into());
|
||||
let root_id = SyncGuid::from(guid.as_str());
|
||||
Ok(bookmarks::public_node::fetch_public_tree(conn, &root_id)?)
|
||||
})
|
||||
}
|
||||
|
@ -481,7 +482,7 @@ pub extern "C" fn bookmarks_get_by_guid(
|
|||
) -> ByteBuffer {
|
||||
log::debug!("bookmarks_get_by_guid");
|
||||
CONNECTIONS.call_with_result(error, handle, |conn| -> places::Result<_> {
|
||||
let guid = SyncGuid(guid.into());
|
||||
let guid = SyncGuid::from(guid.as_str());
|
||||
Ok(bookmarks::public_node::fetch_bookmark(
|
||||
conn,
|
||||
&guid,
|
||||
|
@ -515,7 +516,7 @@ pub unsafe extern "C" fn bookmarks_insert(
|
|||
let bookmark: BookmarkNode = prost::Message::decode(buffer)?;
|
||||
let insertable = bookmark.into_insertable()?;
|
||||
let guid = bookmarks::insert_bookmark(conn, &insertable)?;
|
||||
Ok(guid.0)
|
||||
Ok(guid.into_string())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -540,7 +541,7 @@ pub unsafe extern "C" fn bookmarks_update(
|
|||
pub extern "C" fn bookmarks_delete(handle: u64, id: FfiStr<'_>, error: &mut ExternError) -> u8 {
|
||||
log::debug!("bookmarks_delete");
|
||||
CONNECTIONS.call_with_result(error, handle, |conn| -> places::Result<_> {
|
||||
let guid = SyncGuid(id.into_string());
|
||||
let guid = SyncGuid::from(id.as_str());
|
||||
let did_delete = bookmarks::delete_bookmark(conn, &guid)?;
|
||||
Ok(did_delete)
|
||||
})
|
||||
|
|
|
@ -13,11 +13,11 @@ use crate::storage::{
|
|||
tags::{validate_tag, ValidatedTag},
|
||||
URL_LENGTH_MAX,
|
||||
};
|
||||
use crate::types::SyncGuid;
|
||||
use rusqlite::Connection;
|
||||
use sql_support::{self, ConnExt};
|
||||
use std::iter;
|
||||
use sync15::ServerTimestamp;
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
use url::Url;
|
||||
|
||||
// From Desktop's Ci.nsINavHistoryQueryOptions, but we define it as a str
|
||||
|
@ -92,7 +92,7 @@ impl<'a> IncomingApplicator<'a> {
|
|||
END
|
||||
)"#,
|
||||
&[
|
||||
(":guid", &b.record_id.as_guid().as_ref()),
|
||||
(":guid", &b.record_id.as_guid().as_str()),
|
||||
(":parentGuid", &b.parent_record_id.as_ref().map(BookmarkRecordId::as_guid)),
|
||||
(":serverModified", &(modified.as_millis() as i64)),
|
||||
(":kind", &SyncedBookmarkKind::Bookmark),
|
||||
|
@ -121,7 +121,7 @@ impl<'a> IncomingApplicator<'a> {
|
|||
WHERE guid = :guid),
|
||||
(SELECT id FROM moz_tags
|
||||
WHERE tag = :tag))",
|
||||
&[(":guid", &b.record_id.as_guid().as_ref()), (":tag", t)],
|
||||
&[(":guid", &b.record_id.as_guid().as_str()), (":tag", t)],
|
||||
)?;
|
||||
}
|
||||
};
|
||||
|
@ -136,7 +136,7 @@ impl<'a> IncomingApplicator<'a> {
|
|||
VALUES(:guid, :parentGuid, :serverModified, 1, :kind,
|
||||
:dateAdded, NULLIF(:title, ""))"#,
|
||||
&[
|
||||
(":guid", &f.record_id.as_guid().as_ref()),
|
||||
(":guid", &f.record_id.as_guid().as_str()),
|
||||
(":parentGuid", &f.parent_record_id.as_ref().map(BookmarkRecordId::as_guid)),
|
||||
(":serverModified", &(modified.as_millis() as i64)),
|
||||
(":kind", &SyncedBookmarkKind::Folder),
|
||||
|
@ -171,7 +171,7 @@ impl<'a> IncomingApplicator<'a> {
|
|||
&sql,
|
||||
iter::once(&f.record_id)
|
||||
.chain(chunk.iter())
|
||||
.map(|record_id| record_id.as_guid().as_ref()),
|
||||
.map(|record_id| record_id.as_guid().as_str()),
|
||||
)?;
|
||||
Ok(())
|
||||
},
|
||||
|
@ -273,7 +273,7 @@ impl<'a> IncomingApplicator<'a> {
|
|||
)
|
||||
)"#,
|
||||
&[
|
||||
(":guid", &q.record_id.as_guid().as_ref()),
|
||||
(":guid", &q.record_id.as_guid().as_str()),
|
||||
(":parentGuid", &q.parent_record_id.as_ref().map(BookmarkRecordId::as_guid)),
|
||||
(":serverModified", &(modified.as_millis() as i64)),
|
||||
(":kind", &SyncedBookmarkKind::Query),
|
||||
|
@ -324,7 +324,7 @@ impl<'a> IncomingApplicator<'a> {
|
|||
VALUES(:guid, :parentGuid, :serverModified, 1, :kind,
|
||||
:dateAdded, :title, :feedUrl, :siteUrl, :validity)",
|
||||
&[
|
||||
(":guid", &l.record_id.as_guid().as_ref()),
|
||||
(":guid", &l.record_id.as_guid().as_str()),
|
||||
(
|
||||
":parentGuid",
|
||||
&l.parent_record_id.as_ref().map(BookmarkRecordId::as_guid),
|
||||
|
@ -348,7 +348,7 @@ impl<'a> IncomingApplicator<'a> {
|
|||
VALUES(:guid, :parentGuid, :serverModified, 1, :kind,
|
||||
:dateAdded)",
|
||||
&[
|
||||
(":guid", &s.record_id.as_guid().as_ref()),
|
||||
(":guid", &s.record_id.as_guid().as_str()),
|
||||
(
|
||||
":parentGuid",
|
||||
&s.parent_record_id.as_ref().map(BookmarkRecordId::as_guid),
|
||||
|
@ -469,7 +469,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_apply_folder() {
|
||||
let children = (1..sql_support::default_max_variable_number() * 2)
|
||||
.map(|i| SyncGuid(format!("{:A>12}", i)))
|
||||
.map(|i| SyncGuid::from(format!("{:A>12}", i)))
|
||||
.collect::<Vec<_>>();
|
||||
let value = serde_json::to_value(BookmarkItemRecord::from(FolderRecord {
|
||||
record_id: BookmarkRecordId::from_payload_id("folderAAAAAA".into()),
|
||||
|
|
|
@ -12,9 +12,9 @@ mod tests;
|
|||
use crate::db::PlacesDb;
|
||||
use crate::error::*;
|
||||
use crate::storage::bookmarks::{BookmarkRootGuid, USER_CONTENT_ROOTS};
|
||||
use crate::types::SyncGuid;
|
||||
use rusqlite::types::{ToSql, ToSqlOutput};
|
||||
use rusqlite::Result as RusqliteResult;
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
|
||||
/// Sets up the syncable roots. All items in `moz_bookmarks_synced` descend
|
||||
/// from these roots.
|
||||
|
@ -33,8 +33,8 @@ pub fn create_synced_bookmark_roots(db: &PlacesDb) -> Result<()> {
|
|||
INSERT OR IGNORE INTO moz_bookmarks_synced_structure(
|
||||
guid, parentGuid, position)
|
||||
VALUES('{guid}', '{parent_guid}', {pos});",
|
||||
guid = guid.as_ref(),
|
||||
parent_guid = parent_guid.as_ref(),
|
||||
guid = guid.as_str(),
|
||||
parent_guid = parent_guid.as_str(),
|
||||
kind = SyncedBookmarkKind::Folder as u8,
|
||||
pos = pos
|
||||
))?;
|
||||
|
|
|
@ -4,12 +4,13 @@
|
|||
|
||||
use std::fmt;
|
||||
|
||||
use crate::{storage::bookmarks::BookmarkRootGuid, types::SyncGuid};
|
||||
use crate::storage::bookmarks::BookmarkRootGuid;
|
||||
use serde::{
|
||||
de::{Deserialize, Deserializer, Visitor},
|
||||
ser::{Serialize, Serializer},
|
||||
};
|
||||
use serde_derive::*;
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
|
||||
/// A bookmark record ID. Bookmark record IDs are the same as Places GUIDs,
|
||||
/// except for:
|
||||
|
@ -35,7 +36,7 @@ impl BookmarkRecordId {
|
|||
"toolbar" => BookmarkRootGuid::Toolbar.as_guid(),
|
||||
"unfiled" => BookmarkRootGuid::Unfiled.as_guid(),
|
||||
"mobile" => BookmarkRootGuid::Mobile.as_guid(),
|
||||
_ => SyncGuid(payload_id),
|
||||
_ => SyncGuid::from(payload_id),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -53,7 +54,7 @@ impl BookmarkRecordId {
|
|||
pub fn into_payload_id(self) -> String {
|
||||
self.root_payload_id()
|
||||
.map(Into::into)
|
||||
.unwrap_or_else(|| (self.0).0)
|
||||
.unwrap_or_else(|| (self.0).into_string())
|
||||
}
|
||||
|
||||
/// Returns a reference to the GUID for this record ID.
|
||||
|
|
|
@ -14,7 +14,7 @@ use crate::db::PlacesDb;
|
|||
use crate::error::*;
|
||||
use crate::frecency::{calculate_frecency, DEFAULT_FRECENCY_SETTINGS};
|
||||
use crate::storage::{bookmarks::BookmarkRootGuid, delete_meta, get_meta, put_meta};
|
||||
use crate::types::{BookmarkType, SyncGuid, SyncStatus, Timestamp};
|
||||
use crate::types::{BookmarkType, SyncStatus, Timestamp};
|
||||
use dogear::{
|
||||
self, AbortSignal, Content, Deletion, Item, MergedDescendant, MergedRoot, TelemetryEvent, Tree,
|
||||
UploadReason,
|
||||
|
@ -30,6 +30,7 @@ use sync15::{
|
|||
telemetry, CollSyncIds, CollectionRequest, IncomingChangeset, OutgoingChangeset, Payload,
|
||||
ServerTimestamp, Store, StoreSyncAssociation,
|
||||
};
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
pub const LAST_SYNC_META_KEY: &str = "bookmarks_last_sync_time";
|
||||
// Note that all engines in this crate should use a *different* meta key
|
||||
// for the global sync ID, because engines are reset individually.
|
||||
|
@ -254,7 +255,7 @@ impl<'a> BookmarksStore<'a> {
|
|||
(s.syncChangeCounter > 0 OR w.id NOT NULL)",
|
||||
local_items_fragment = LocalItemsFragment("localItems"),
|
||||
kind = item_kind_fragment("s.type", UrlOrPlaceIdFragment::Url("h.url")),
|
||||
root_guid = BookmarkRootGuid::Root.as_guid().as_ref(),
|
||||
root_guid = BookmarkRootGuid::Root.as_guid().as_str(),
|
||||
))?;
|
||||
|
||||
// Record the child GUIDs of locally changed folders, which we use to
|
||||
|
@ -666,7 +667,7 @@ struct Driver {
|
|||
|
||||
impl dogear::Driver for Driver {
|
||||
fn generate_new_guid(&self, _invalid_guid: &dogear::Guid) -> dogear::Result<dogear::Guid> {
|
||||
Ok(SyncGuid::new().into())
|
||||
Ok(SyncGuid::random().as_str().into())
|
||||
}
|
||||
|
||||
fn record_telemetry_event(&self, event: TelemetryEvent) {
|
||||
|
@ -758,7 +759,7 @@ impl<'a> Merger<'a> {
|
|||
fn local_row_to_item(&self, row: &Row<'_>) -> Result<Item> {
|
||||
let guid = row.get::<_, SyncGuid>("guid")?;
|
||||
let kind = SyncedBookmarkKind::from_u8(row.get("kind")?)?;
|
||||
let mut item = Item::new(guid.into(), kind.into());
|
||||
let mut item = Item::new(guid.as_str().into(), kind.into());
|
||||
// Note that this doesn't account for local clock skew.
|
||||
let age = self
|
||||
.local_time
|
||||
|
@ -773,7 +774,7 @@ impl<'a> Merger<'a> {
|
|||
fn remote_row_to_item(&self, row: &Row<'_>) -> Result<Item> {
|
||||
let guid = row.get::<_, SyncGuid>("guid")?;
|
||||
let kind = SyncedBookmarkKind::from_u8(row.get("kind")?)?;
|
||||
let mut item = Item::new(guid.into(), kind.into());
|
||||
let mut item = Item::new(guid.as_str().into(), kind.into());
|
||||
// note that serverModified in this table is an int with ms, which isn't
|
||||
// the format of a ServerTimestamp - so we convert it into a number
|
||||
// of seconds before creating a ServerTimestamp and doing duration_since.
|
||||
|
@ -817,7 +818,7 @@ impl<'a> dogear::Store<Error> for Merger<'a> {
|
|||
let parent_guid = row.get::<_, SyncGuid>("parentGuid")?;
|
||||
builder
|
||||
.item(self.local_row_to_item(&row)?)?
|
||||
.by_structure(&parent_guid.into())?;
|
||||
.by_structure(&parent_guid.as_str().into())?;
|
||||
}
|
||||
|
||||
let mut tree = Tree::try_from(builder)?;
|
||||
|
@ -831,7 +832,7 @@ impl<'a> dogear::Store<Error> for Merger<'a> {
|
|||
while let Some(row) = results.next()? {
|
||||
self.store.interruptee.err_if_interrupted()?;
|
||||
let guid = row.get::<_, SyncGuid>("guid")?;
|
||||
tree.note_deleted(guid.into());
|
||||
tree.note_deleted(guid.as_str().into());
|
||||
}
|
||||
|
||||
Ok(tree)
|
||||
|
@ -853,7 +854,7 @@ impl<'a> dogear::Store<Error> for Merger<'a> {
|
|||
WHERE v.guid IS NULL AND
|
||||
p.guid <> '{root_guid}' AND
|
||||
b.syncStatus <> {sync_status}",
|
||||
root_guid = BookmarkRootGuid::Root.as_guid().as_ref(),
|
||||
root_guid = BookmarkRootGuid::Root.as_guid().as_str(),
|
||||
sync_status = SyncStatus::Normal as u8
|
||||
);
|
||||
let mut stmt = self.store.db.prepare(&sql)?;
|
||||
|
@ -882,7 +883,7 @@ impl<'a> dogear::Store<Error> for Merger<'a> {
|
|||
}
|
||||
};
|
||||
let guid = row.get::<_, SyncGuid>("guid")?;
|
||||
contents.insert(guid.into(), content);
|
||||
contents.insert(guid.as_str().into(), content);
|
||||
}
|
||||
|
||||
Ok(contents)
|
||||
|
@ -898,7 +899,7 @@ impl<'a> dogear::Store<Error> for Merger<'a> {
|
|||
FROM moz_bookmarks_synced
|
||||
WHERE NOT isDeleted AND
|
||||
guid = '{root_guid}'",
|
||||
root_guid = BookmarkRootGuid::Root.as_guid().as_ref()
|
||||
root_guid = BookmarkRootGuid::Root.as_guid().as_str()
|
||||
);
|
||||
let mut builder = self
|
||||
.store
|
||||
|
@ -921,7 +922,7 @@ impl<'a> dogear::Store<Error> for Merger<'a> {
|
|||
WHERE NOT isDeleted AND
|
||||
guid <> '{root_guid}'
|
||||
ORDER BY guid",
|
||||
root_guid = BookmarkRootGuid::Root.as_guid().as_ref()
|
||||
root_guid = BookmarkRootGuid::Root.as_guid().as_str()
|
||||
);
|
||||
let mut stmt = self.store.db.prepare(&sql)?;
|
||||
let mut results = stmt.query(NO_PARAMS)?;
|
||||
|
@ -929,7 +930,7 @@ impl<'a> dogear::Store<Error> for Merger<'a> {
|
|||
self.store.interruptee.err_if_interrupted()?;
|
||||
let p = builder.item(self.remote_row_to_item(&row)?)?;
|
||||
if let Some(parent_guid) = row.get::<_, Option<SyncGuid>>("parentGuid")? {
|
||||
p.by_parent_guid(parent_guid.into())?;
|
||||
p.by_parent_guid(parent_guid.as_str().into())?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -937,7 +938,7 @@ impl<'a> dogear::Store<Error> for Merger<'a> {
|
|||
"SELECT guid, parentGuid FROM moz_bookmarks_synced_structure
|
||||
WHERE guid <> '{root_guid}'
|
||||
ORDER BY parentGuid, position",
|
||||
root_guid = BookmarkRootGuid::Root.as_guid().as_ref()
|
||||
root_guid = BookmarkRootGuid::Root.as_guid().as_str()
|
||||
);
|
||||
let mut stmt = self.store.db.prepare(&sql)?;
|
||||
let mut results = stmt.query(NO_PARAMS)?;
|
||||
|
@ -946,8 +947,8 @@ impl<'a> dogear::Store<Error> for Merger<'a> {
|
|||
let guid = row.get::<_, SyncGuid>("guid")?;
|
||||
let parent_guid = row.get::<_, SyncGuid>("parentGuid")?;
|
||||
builder
|
||||
.parent_for(&guid.into())
|
||||
.by_children(&parent_guid.into())?;
|
||||
.parent_for(&guid.as_str().into())
|
||||
.by_children(&parent_guid.as_str().into())?;
|
||||
}
|
||||
|
||||
let mut tree = Tree::try_from(builder)?;
|
||||
|
@ -961,7 +962,7 @@ impl<'a> dogear::Store<Error> for Merger<'a> {
|
|||
while let Some(row) = results.next()? {
|
||||
self.store.interruptee.err_if_interrupted()?;
|
||||
let guid = row.get::<_, SyncGuid>("guid")?;
|
||||
tree.note_deleted(guid.into());
|
||||
tree.note_deleted(guid.as_str().into());
|
||||
}
|
||||
|
||||
Ok(tree)
|
||||
|
@ -983,7 +984,7 @@ impl<'a> dogear::Store<Error> for Merger<'a> {
|
|||
v.needsMerge AND
|
||||
b.guid IS NULL AND
|
||||
s.parentGuid <> '{root_guid}'",
|
||||
root_guid = BookmarkRootGuid::Root.as_guid().as_ref()
|
||||
root_guid = BookmarkRootGuid::Root.as_guid().as_str()
|
||||
);
|
||||
let mut stmt = self.store.db.prepare(&sql)?;
|
||||
let mut results = stmt.query(NO_PARAMS)?;
|
||||
|
@ -1008,7 +1009,7 @@ impl<'a> dogear::Store<Error> for Merger<'a> {
|
|||
_ => continue,
|
||||
};
|
||||
let guid = row.get::<_, SyncGuid>("guid")?;
|
||||
contents.insert(guid.into(), content);
|
||||
contents.insert(guid.as_str().into(), content);
|
||||
}
|
||||
|
||||
Ok(contents)
|
||||
|
@ -1064,7 +1065,7 @@ impl<'a> fmt::Display for LocalItemsFragment<'a> {
|
|||
FROM moz_bookmarks b
|
||||
JOIN {name} s ON s.id = b.parent)",
|
||||
name = self.0,
|
||||
root_guid = BookmarkRootGuid::Root.as_guid().as_ref()
|
||||
root_guid = BookmarkRootGuid::Root.as_guid().as_str()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1323,7 +1324,7 @@ mod tests {
|
|||
assert_eq!(node.is_syncable(), true);
|
||||
|
||||
let node = tree
|
||||
.node_for_guid(&BookmarkRootGuid::Unfiled.as_guid().into())
|
||||
.node_for_guid(&BookmarkRootGuid::Unfiled.as_guid().as_str().into())
|
||||
.expect("should exist");
|
||||
assert_eq!(node.needs_merge, true);
|
||||
assert_eq!(node.validity, Validity::Valid);
|
||||
|
@ -1331,7 +1332,7 @@ mod tests {
|
|||
assert_eq!(node.is_syncable(), true);
|
||||
|
||||
let node = tree
|
||||
.node_for_guid(&BookmarkRootGuid::Menu.as_guid().into())
|
||||
.node_for_guid(&BookmarkRootGuid::Menu.as_guid().as_str().into())
|
||||
.expect("should exist");
|
||||
assert_eq!(node.needs_merge, false);
|
||||
assert_eq!(node.validity, Validity::Valid);
|
||||
|
@ -1339,7 +1340,7 @@ mod tests {
|
|||
assert_eq!(node.is_syncable(), true);
|
||||
|
||||
let node = tree
|
||||
.node_for_guid(&BookmarkRootGuid::Root.as_guid().into())
|
||||
.node_for_guid(&BookmarkRootGuid::Root.as_guid().as_str().into())
|
||||
.expect("should exist");
|
||||
assert_eq!(node.validity, Validity::Valid);
|
||||
assert_eq!(node.level(), 0);
|
||||
|
@ -1391,21 +1392,21 @@ mod tests {
|
|||
assert_eq!(node.is_syncable(), true);
|
||||
|
||||
let node = tree
|
||||
.node_for_guid(&BookmarkRootGuid::Unfiled.as_guid().into())
|
||||
.node_for_guid(&BookmarkRootGuid::Unfiled.as_guid().as_str().into())
|
||||
.expect("should exist");
|
||||
assert_eq!(node.needs_merge, true);
|
||||
assert_eq!(node.level(), 1);
|
||||
assert_eq!(node.is_syncable(), true);
|
||||
|
||||
let node = tree
|
||||
.node_for_guid(&BookmarkRootGuid::Menu.as_guid().into())
|
||||
.node_for_guid(&BookmarkRootGuid::Menu.as_guid().as_str().into())
|
||||
.expect("should exist");
|
||||
assert_eq!(node.needs_merge, false);
|
||||
assert_eq!(node.level(), 1);
|
||||
assert_eq!(node.is_syncable(), true);
|
||||
|
||||
let node = tree
|
||||
.node_for_guid(&BookmarkRootGuid::Root.as_guid().into())
|
||||
.node_for_guid(&BookmarkRootGuid::Root.as_guid().as_str().into())
|
||||
.expect("should exist");
|
||||
assert_eq!(node.needs_merge, false);
|
||||
assert_eq!(node.level(), 0);
|
||||
|
|
|
@ -7,9 +7,10 @@ use crate::{
|
|||
db::PlacesDb,
|
||||
error::*,
|
||||
storage::RowId,
|
||||
types::{SyncGuid, Timestamp},
|
||||
types::Timestamp,
|
||||
};
|
||||
use rusqlite::Row;
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
|
||||
use sql_support::{self, ConnExt};
|
||||
use sync15::ServerTimestamp;
|
||||
|
@ -205,7 +206,7 @@ impl SyncedBookmarkItem {
|
|||
let parts = t.splitn(2, ':').collect::<Vec<_>>();
|
||||
(
|
||||
parts[0].parse::<i64>().unwrap(),
|
||||
SyncGuid(parts[1].to_owned()),
|
||||
SyncGuid::from(parts[1].to_owned()),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
|
|
|
@ -199,8 +199,9 @@ mod sql_fns {
|
|||
use crate::api::matcher::{split_after_host_and_port, split_after_prefix};
|
||||
use crate::hash;
|
||||
use crate::match_impl::{AutocompleteMatch, MatchBehavior, SearchBehavior};
|
||||
use crate::types::{SyncGuid, Timestamp};
|
||||
use crate::types::Timestamp;
|
||||
use rusqlite::{functions::Context, types::ValueRef, Error, Result};
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
|
||||
// Helpers for define_functions
|
||||
fn get_raw_str<'a>(ctx: &'a Context<'_>, fname: &'static str, idx: usize) -> Result<&'a str> {
|
||||
|
@ -343,7 +344,7 @@ mod sql_fns {
|
|||
|
||||
#[inline(never)]
|
||||
pub fn generate_guid(_ctx: &Context<'_>) -> Result<SyncGuid> {
|
||||
Ok(SyncGuid::new())
|
||||
Ok(SyncGuid::random())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -243,7 +243,7 @@ pub fn create(db: &PlacesDb) -> Result<()> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::db::PlacesDb;
|
||||
use crate::types::SyncGuid;
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
use url::Url;
|
||||
|
||||
#[test]
|
||||
|
@ -267,7 +267,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_places_no_tombstone() {
|
||||
let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
|
||||
let guid = SyncGuid::new();
|
||||
let guid = SyncGuid::random();
|
||||
|
||||
conn.execute_named_cached(
|
||||
"INSERT INTO moz_places (guid, url, url_hash) VALUES (:guid, :url, hash(:url))",
|
||||
|
@ -297,7 +297,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_places_tombstone_removal() {
|
||||
let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
|
||||
let guid = SyncGuid::new();
|
||||
let guid = SyncGuid::random();
|
||||
|
||||
conn.execute_named_cached(
|
||||
"INSERT INTO moz_places_tombstones VALUES (:guid)",
|
||||
|
@ -398,11 +398,11 @@ mod tests {
|
|||
fn test_bookmark_foreign_count_triggers() {
|
||||
// create the place.
|
||||
let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
|
||||
let guid1 = SyncGuid::new();
|
||||
let guid1 = SyncGuid::random();
|
||||
let url1 = Url::parse("http://example.com")
|
||||
.expect("valid url")
|
||||
.into_string();
|
||||
let guid2 = SyncGuid::new();
|
||||
let guid2 = SyncGuid::random();
|
||||
let url2 = Url::parse("http://example2.com")
|
||||
.expect("valid url")
|
||||
.into_string();
|
||||
|
|
|
@ -11,14 +11,14 @@ use crate::storage::history::history_sync::{
|
|||
apply_synced_deletion, apply_synced_reconciliation, apply_synced_visits, fetch_outgoing,
|
||||
fetch_visits, finish_incoming, finish_outgoing, FetchedVisit, FetchedVisitPage, OutgoingInfo,
|
||||
};
|
||||
use crate::types::{SyncGuid, Timestamp, VisitTransition};
|
||||
use crate::valid_guid::is_valid_places_guid;
|
||||
use crate::types::{Timestamp, VisitTransition};
|
||||
use interrupt::Interruptee;
|
||||
use serde_json;
|
||||
use std::collections::HashSet;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use sync15::telemetry;
|
||||
use sync15::{IncomingChangeset, OutgoingChangeset, Payload};
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
use url::Url;
|
||||
|
||||
/// Clamps a history visit date between the current date and the earliest
|
||||
|
@ -65,7 +65,7 @@ fn plan_incoming_record(conn: &PlacesDb, record: HistoryRecord, max_visits: usiz
|
|||
Err(e) => return IncomingPlan::Invalid(e.into()),
|
||||
};
|
||||
|
||||
if !is_valid_places_guid(record.id.as_ref()) {
|
||||
if !record.id.is_valid_for_places() {
|
||||
return IncomingPlan::Invalid(InvalidPlaceInfo::InvalidGuid.into());
|
||||
}
|
||||
|
||||
|
@ -260,7 +260,9 @@ pub fn apply_plan(
|
|||
for (guid, out_record) in out_infos.drain() {
|
||||
let payload = match out_record {
|
||||
OutgoingInfo::Record(record) => Payload::from_record(record)?,
|
||||
OutgoingInfo::Tombstone => Payload::new_tombstone_with_ttl(guid.0.clone(), HISTORY_TTL),
|
||||
OutgoingInfo::Tombstone => {
|
||||
Payload::new_tombstone_with_ttl(guid.as_str().to_string(), HISTORY_TTL)
|
||||
}
|
||||
};
|
||||
log::trace!("outgoing {:?}", payload);
|
||||
outgoing.changes.push(payload);
|
||||
|
@ -451,7 +453,7 @@ mod tests {
|
|||
|
||||
// try and add an incoming record with the same URL but different guid.
|
||||
let record = HistoryRecord {
|
||||
id: SyncGuid::new(),
|
||||
id: SyncGuid::random(),
|
||||
title: "title".into(),
|
||||
hist_uri: "https://example.com".into(),
|
||||
sortindex: 0,
|
||||
|
@ -475,10 +477,10 @@ mod tests {
|
|||
// This is testing the case when there are no local visits to that URL.
|
||||
let _ = env_logger::try_init();
|
||||
let db = PlacesDb::open_in_memory(ConnectionType::Sync)?;
|
||||
let guid1 = SyncGuid::new();
|
||||
let guid1 = SyncGuid::random();
|
||||
let ts1: Timestamp = (SystemTime::now() - Duration::new(5, 0)).into();
|
||||
|
||||
let guid2 = SyncGuid::new();
|
||||
let guid2 = SyncGuid::random();
|
||||
let ts2: Timestamp = SystemTime::now().into();
|
||||
let url = Url::parse("https://example.com")?;
|
||||
|
||||
|
@ -535,10 +537,10 @@ mod tests {
|
|||
let _ = env_logger::try_init();
|
||||
let db = PlacesDb::open_in_memory(ConnectionType::Sync)?;
|
||||
|
||||
let guid1 = SyncGuid::new();
|
||||
let guid1 = SyncGuid::random();
|
||||
let ts1: Timestamp = (SystemTime::now() - Duration::new(5, 0)).into();
|
||||
|
||||
let guid2 = SyncGuid::new();
|
||||
let guid2 = SyncGuid::random();
|
||||
let ts2: Timestamp = SystemTime::now().into();
|
||||
let url = Url::parse("https://example.com")?;
|
||||
|
||||
|
@ -597,10 +599,10 @@ mod tests {
|
|||
let _ = env_logger::try_init();
|
||||
let db = PlacesDb::open_in_memory(ConnectionType::Sync)?;
|
||||
|
||||
let guid1 = SyncGuid::new();
|
||||
let guid1 = SyncGuid::random();
|
||||
let ts1: Timestamp = (SystemTime::now() - Duration::new(5, 0)).into();
|
||||
|
||||
let guid2 = SyncGuid::new();
|
||||
let guid2 = SyncGuid::random();
|
||||
let ts2: Timestamp = SystemTime::now().into();
|
||||
let url = Url::parse("https://example.com")?;
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
use super::ServerVisitTimestamp;
|
||||
use crate::error::*;
|
||||
use crate::types::SyncGuid;
|
||||
use serde_derive::*;
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
|
@ -23,7 +23,6 @@ pub mod storage;
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod util;
|
||||
mod valid_guid;
|
||||
|
||||
pub mod msg_types {
|
||||
include!(concat!(env!("OUT_DIR"), "/msg_types.rs"));
|
||||
|
|
|
@ -6,7 +6,7 @@ use super::RowId;
|
|||
use super::{fetch_page_info, new_page_info};
|
||||
use crate::db::PlacesDb;
|
||||
use crate::error::*;
|
||||
use crate::types::{BookmarkType, SyncGuid, SyncStatus, Timestamp};
|
||||
use crate::types::{BookmarkType, SyncStatus, Timestamp};
|
||||
use rusqlite::types::ToSql;
|
||||
use rusqlite::{Connection, Row};
|
||||
use serde::{
|
||||
|
@ -19,6 +19,7 @@ use serde_json::{self, json};
|
|||
use sql_support::{self, ConnExt};
|
||||
use std::cmp::{max, min};
|
||||
use std::collections::HashMap;
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
use url::Url;
|
||||
|
||||
pub use public_node::PublicNode;
|
||||
|
@ -45,7 +46,7 @@ fn create_root(
|
|||
(SELECT id FROM moz_bookmarks WHERE guid = {:?}),
|
||||
1, :sync_status)
|
||||
",
|
||||
BookmarkRootGuid::Root.as_guid().as_ref()
|
||||
BookmarkRootGuid::Root.as_guid().as_str()
|
||||
);
|
||||
let params: Vec<(&str, &dyn ToSql)> = vec![
|
||||
(":item_type", &BookmarkType::Folder),
|
||||
|
@ -290,7 +291,7 @@ fn insert_bookmark_in_tx(db: &PlacesDb, bm: &InsertableItem) -> Result<SyncGuid>
|
|||
(:fk, :type, :parent, :position, :title, :dateAdded, :lastModified,
|
||||
:guid, :syncStatus, :syncChangeCounter)";
|
||||
|
||||
let guid = bm.guid().clone().unwrap_or_else(SyncGuid::new);
|
||||
let guid = bm.guid().clone().unwrap_or_else(SyncGuid::random);
|
||||
let date_added = bm.date_added().unwrap_or_else(Timestamp::now);
|
||||
// last_modified can't be before date_added
|
||||
let last_modified = max(
|
||||
|
@ -375,7 +376,7 @@ pub fn delete_bookmark(db: &PlacesDb, guid: &SyncGuid) -> Result<bool> {
|
|||
|
||||
fn delete_bookmark_in_tx(db: &PlacesDb, guid: &SyncGuid) -> Result<bool> {
|
||||
// Can't delete a root.
|
||||
if let Some(root) = guid.as_root() {
|
||||
if let Some(root) = BookmarkRootGuid::well_known(&guid.as_str()) {
|
||||
return Err(InvalidPlaceInfo::CannotUpdateRoot(root).into());
|
||||
}
|
||||
let record = match get_raw_bookmark(db, guid)? {
|
||||
|
@ -500,7 +501,8 @@ fn update_bookmark_in_tx(
|
|||
item: &UpdatableItem,
|
||||
raw: RawBookmark,
|
||||
) -> Result<()> {
|
||||
if guid.is_root() {
|
||||
// if guid is root
|
||||
if BookmarkRootGuid::well_known(&guid.as_str()).is_some() {
|
||||
return Err(InvalidPlaceInfo::CannotUpdateRoot(BookmarkRootGuid::Root).into());
|
||||
}
|
||||
let existing_parent_guid = raw
|
||||
|
@ -905,7 +907,7 @@ mod test_serialize {
|
|||
|
||||
#[test]
|
||||
fn test_tree_serialize() -> Result<()> {
|
||||
let guid = SyncGuid::new();
|
||||
let guid = SyncGuid::random();
|
||||
let tree = BookmarkTreeNode::Folder(FolderNode {
|
||||
guid: Some(guid.clone()),
|
||||
date_added: None,
|
||||
|
@ -1031,7 +1033,7 @@ fn add_subtree_infos(parent: &SyncGuid, tree: &FolderNode, insert_infos: &mut Ve
|
|||
.into(),
|
||||
),
|
||||
BookmarkTreeNode::Folder(f) => {
|
||||
let my_guid = f.guid.clone().unwrap_or_else(SyncGuid::new);
|
||||
let my_guid = f.guid.clone().unwrap_or_else(SyncGuid::random);
|
||||
// must add the folder before we recurse into children.
|
||||
insert_infos.push(
|
||||
InsertableFolder {
|
||||
|
@ -1485,10 +1487,10 @@ mod tests {
|
|||
let _ = env_logger::try_init();
|
||||
let conn = new_mem_connection();
|
||||
|
||||
let guid1 = SyncGuid::new();
|
||||
let guid2 = SyncGuid::new();
|
||||
let guid2_1 = SyncGuid::new();
|
||||
let guid3 = SyncGuid::new();
|
||||
let guid1 = SyncGuid::random();
|
||||
let guid2 = SyncGuid::random();
|
||||
let guid2_1 = SyncGuid::random();
|
||||
let guid3 = SyncGuid::random();
|
||||
|
||||
let jtree = json!({
|
||||
"guid": &BookmarkRootGuid::Unfiled.as_guid(),
|
||||
|
|
|
@ -10,7 +10,8 @@ use super::{
|
|||
|
||||
use crate::error::{InvalidPlaceInfo, Result};
|
||||
use crate::msg_types;
|
||||
use crate::types::{BookmarkType, SyncGuid};
|
||||
use crate::types::BookmarkType;
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
use url::Url;
|
||||
|
||||
impl From<BookmarkTreeNode> for PublicNode {
|
||||
|
@ -66,17 +67,17 @@ impl From<PublicNode> for msg_types::BookmarkNode {
|
|||
};
|
||||
Self {
|
||||
node_type: Some(n.node_type as i32),
|
||||
guid: Some(n.guid.0),
|
||||
guid: Some(n.guid.into_string()),
|
||||
date_added: Some(n.date_added.0 as i64),
|
||||
last_modified: Some(n.last_modified.0 as i64),
|
||||
title: n.title,
|
||||
url: n.url.map(url::Url::into_string),
|
||||
parent_guid: n.parent_guid.map(|g| g.0),
|
||||
parent_guid: n.parent_guid.map(|g| g.into_string()),
|
||||
position: Some(n.position),
|
||||
child_guids: n.child_guids.map_or(vec![], |child_guids| {
|
||||
child_guids
|
||||
.into_iter()
|
||||
.map(|m| m.0)
|
||||
.map(|m| m.into_string())
|
||||
.collect::<Vec<String>>()
|
||||
}),
|
||||
child_nodes: n.child_nodes.map_or(vec![], |nodes| {
|
||||
|
@ -243,10 +244,10 @@ impl From<msg_types::BookmarkNode> for BookmarkUpdateInfo {
|
|||
Self {
|
||||
// This is a bug in our code on the other side of the FFI,
|
||||
// so expect should be fine.
|
||||
guid: SyncGuid(n.guid.expect("Missing guid")),
|
||||
guid: SyncGuid::from(n.guid.expect("Missing guid")),
|
||||
title: n.title,
|
||||
url: n.url,
|
||||
parent_guid: n.parent_guid.map(SyncGuid),
|
||||
parent_guid: n.parent_guid.map(SyncGuid::from),
|
||||
position: n.position,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ impl Default for PublicNode {
|
|||
// Note: we mainly want `Default::default()` for filling in the
|
||||
// missing part of struct decls.
|
||||
node_type: BookmarkType::Separator,
|
||||
guid: SyncGuid(String::default()),
|
||||
guid: SyncGuid::from(""),
|
||||
parent_guid: None,
|
||||
position: 0,
|
||||
date_added: Timestamp(0),
|
||||
|
@ -310,7 +310,7 @@ mod test {
|
|||
);
|
||||
let url = url::Url::parse("https://www.example2.com/a/b/c/d?q=v#abcde")?;
|
||||
let mut bmks = fetch_bookmarks_by_url(&conns.read, &url)?;
|
||||
bmks.sort_by_key(|b| b.guid.0.clone());
|
||||
bmks.sort_by_key(|b| b.guid.as_str().to_string());
|
||||
assert_eq!(bmks.len(), 2);
|
||||
assert_eq!(
|
||||
bmks[0],
|
||||
|
@ -395,7 +395,7 @@ mod test {
|
|||
}),
|
||||
);
|
||||
let mut bmks = search_bookmarks(&conns.read, "ample", 10)?;
|
||||
bmks.sort_by_key(|b| b.guid.0.clone());
|
||||
bmks.sort_by_key(|b| b.guid.as_str().to_string());
|
||||
assert_eq!(bmks.len(), 6);
|
||||
let expect = [
|
||||
("bookmark1___", "https://www.example1.com/", "", 0),
|
||||
|
@ -426,7 +426,7 @@ mod test {
|
|||
),
|
||||
];
|
||||
for (got, want) in bmks.iter().zip(expect.iter()) {
|
||||
assert_eq!(&got.guid.0, want.0);
|
||||
assert_eq!(got.guid.as_str(), want.0);
|
||||
assert_eq!(got.url.as_ref().unwrap(), &url::Url::parse(want.1).unwrap());
|
||||
assert_eq!(got.title.as_ref().unwrap_or(&String::new()), want.2);
|
||||
assert_eq!(got.position, want.3);
|
||||
|
@ -478,8 +478,8 @@ mod test {
|
|||
assert_eq!(
|
||||
child.child_guids.unwrap(),
|
||||
&[
|
||||
SyncGuid("bookmark1___".into()),
|
||||
SyncGuid("bookmark2___".into())
|
||||
SyncGuid::from("bookmark1___"),
|
||||
SyncGuid::from("bookmark2___")
|
||||
]
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
* 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/. */
|
||||
|
||||
use crate::SyncGuid;
|
||||
use lazy_static::lazy_static;
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
|
||||
pub const USER_CONTENT_ROOTS: &[BookmarkRootGuid] = &[
|
||||
BookmarkRootGuid::Menu,
|
||||
|
@ -28,23 +28,23 @@ lazy_static! {
|
|||
static ref GUIDS: [(BookmarkRootGuid, SyncGuid); 5] = [
|
||||
(
|
||||
BookmarkRootGuid::Root,
|
||||
SyncGuid(BookmarkRootGuid::Root.as_str().into())
|
||||
SyncGuid::from(BookmarkRootGuid::Root.as_str())
|
||||
),
|
||||
(
|
||||
BookmarkRootGuid::Menu,
|
||||
SyncGuid(BookmarkRootGuid::Menu.as_str().into())
|
||||
SyncGuid::from(BookmarkRootGuid::Menu.as_str())
|
||||
),
|
||||
(
|
||||
BookmarkRootGuid::Toolbar,
|
||||
SyncGuid(BookmarkRootGuid::Toolbar.as_str().into())
|
||||
SyncGuid::from(BookmarkRootGuid::Toolbar.as_str())
|
||||
),
|
||||
(
|
||||
BookmarkRootGuid::Unfiled,
|
||||
SyncGuid(BookmarkRootGuid::Unfiled.as_str().into())
|
||||
SyncGuid::from(BookmarkRootGuid::Unfiled.as_str())
|
||||
),
|
||||
(
|
||||
BookmarkRootGuid::Mobile,
|
||||
SyncGuid(BookmarkRootGuid::Mobile.as_str().into())
|
||||
SyncGuid::from(BookmarkRootGuid::Mobile.as_str())
|
||||
),
|
||||
];
|
||||
}
|
||||
|
@ -71,12 +71,12 @@ impl BookmarkRootGuid {
|
|||
pub fn well_known(guid: &str) -> Option<Self> {
|
||||
GUIDS
|
||||
.iter()
|
||||
.find(|(_, sync_guid)| sync_guid.0 == guid)
|
||||
.find(|(_, sync_guid)| sync_guid.as_str() == guid)
|
||||
.map(|(root, _)| *root)
|
||||
}
|
||||
|
||||
pub fn from_guid(guid: &SyncGuid) -> Option<Self> {
|
||||
Self::well_known(&guid.0)
|
||||
Self::well_known(guid.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,26 +89,26 @@ impl From<BookmarkRootGuid> for SyncGuid {
|
|||
// Allow comparisons between BookmarkRootGuid and SyncGuids
|
||||
impl PartialEq<BookmarkRootGuid> for SyncGuid {
|
||||
fn eq(&self, other: &BookmarkRootGuid) -> bool {
|
||||
self.0 == other.as_str()
|
||||
self.as_str().as_bytes() == other.as_str().as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<SyncGuid> for BookmarkRootGuid {
|
||||
fn eq(&self, other: &SyncGuid) -> bool {
|
||||
other.0 == self.as_str()
|
||||
other.as_str().as_bytes() == self.as_str().as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
// Even if we have a reference to &SyncGuid
|
||||
impl<'a> PartialEq<BookmarkRootGuid> for &'a SyncGuid {
|
||||
fn eq(&self, other: &BookmarkRootGuid) -> bool {
|
||||
self.0 == other.as_str()
|
||||
self.as_str().as_bytes() == other.as_str().as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq<&'a SyncGuid> for BookmarkRootGuid {
|
||||
fn eq(&self, other: &&'a SyncGuid) -> bool {
|
||||
other.0 == self.as_str()
|
||||
other.as_str().as_bytes() == self.as_str().as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,11 +10,12 @@ use crate::hash;
|
|||
use crate::msg_types::{HistoryVisitInfo, HistoryVisitInfos};
|
||||
use crate::observation::VisitObservation;
|
||||
use crate::storage::{delete_pending_temp_tables, get_meta, put_meta};
|
||||
use crate::types::{SyncGuid, SyncStatus, Timestamp, VisitTransition, VisitTransitionSet};
|
||||
use crate::types::{SyncStatus, Timestamp, VisitTransition, VisitTransitionSet};
|
||||
use rusqlite::types::ToSql;
|
||||
use rusqlite::Result as RusqliteResult;
|
||||
use rusqlite::{Row, NO_PARAMS};
|
||||
use sql_support::{self, ConnExt};
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
use url::Url;
|
||||
|
||||
/// When `delete_everything` is called (to perform a permanent local deletion), in
|
||||
|
@ -2069,7 +2070,7 @@ mod tests {
|
|||
|
||||
apply_synced_visits(
|
||||
&conn,
|
||||
&SyncGuid::new(),
|
||||
&SyncGuid::random(),
|
||||
&url::Url::parse("http://www.example.com/123").unwrap(),
|
||||
&None,
|
||||
&[
|
||||
|
@ -2101,7 +2102,7 @@ mod tests {
|
|||
// Check that we don't insert a place if all visits are too old.
|
||||
apply_synced_visits(
|
||||
&conn,
|
||||
&SyncGuid::new(),
|
||||
&SyncGuid::random(),
|
||||
&url::Url::parse("http://www.example.com/1234").unwrap(),
|
||||
&None,
|
||||
&[HistoryRecordVisit {
|
||||
|
|
|
@ -12,13 +12,14 @@ pub mod tags;
|
|||
use crate::db::PlacesDb;
|
||||
use crate::error::{ErrorKind, InvalidPlaceInfo, Result};
|
||||
use crate::msg_types::HistoryVisitInfo;
|
||||
use crate::types::{SyncGuid, SyncStatus, Timestamp, VisitTransition};
|
||||
use crate::types::{SyncStatus, Timestamp, VisitTransition};
|
||||
use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||
use rusqlite::Result as RusqliteResult;
|
||||
use rusqlite::Row;
|
||||
use serde_derive::*;
|
||||
use sql_support::{self, ConnExt};
|
||||
use std::fmt;
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
use url::Url;
|
||||
|
||||
/// From https://searchfox.org/mozilla-central/rev/93905b660f/toolkit/components/places/PlacesUtils.jsm#189
|
||||
|
@ -146,7 +147,7 @@ fn fetch_page_info(db: &PlacesDb, url: &Url) -> Result<Option<FetchedPageInfo>>
|
|||
fn new_page_info(db: &PlacesDb, url: &Url, new_guid: Option<SyncGuid>) -> Result<PageInfo> {
|
||||
let guid = match new_guid {
|
||||
Some(guid) => guid,
|
||||
None => SyncGuid::new(),
|
||||
None => SyncGuid::random(),
|
||||
};
|
||||
let url_str = url.as_str();
|
||||
if url_str.len() > URL_LENGTH_MAX {
|
||||
|
|
|
@ -8,8 +8,8 @@ use serde_json::Value;
|
|||
use crate::{
|
||||
db::PlacesDb,
|
||||
storage::bookmarks::{fetch_tree, insert_tree, BookmarkTreeNode, FetchDepth},
|
||||
types::SyncGuid,
|
||||
};
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
* 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/. */
|
||||
|
||||
use crate::storage::bookmarks::BookmarkRootGuid;
|
||||
use dogear;
|
||||
use failure::Fail;
|
||||
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||
use rusqlite::Result as RusqliteResult;
|
||||
|
@ -16,65 +14,6 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|||
mod visit_transition_set;
|
||||
pub use visit_transition_set::VisitTransitionSet;
|
||||
|
||||
// XXX - copied from logins - surprised it's not in `sync`
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct SyncGuid(pub String);
|
||||
|
||||
impl SyncGuid {
|
||||
#[allow(clippy::new_without_default)] // This probably should not be called `new`...
|
||||
pub fn new() -> Self {
|
||||
SyncGuid(sync15::random_guid().unwrap())
|
||||
}
|
||||
|
||||
pub fn as_root(&self) -> Option<BookmarkRootGuid> {
|
||||
BookmarkRootGuid::well_known(&self.0)
|
||||
}
|
||||
|
||||
pub fn is_root(&self) -> bool {
|
||||
BookmarkRootGuid::well_known(&self.0).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for SyncGuid {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for SyncGuid
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
fn from(x: T) -> SyncGuid {
|
||||
SyncGuid(x.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SyncGuid> for dogear::Guid {
|
||||
fn from(guid: SyncGuid) -> dogear::Guid {
|
||||
guid.as_ref().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for SyncGuid {
|
||||
fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>> {
|
||||
Ok(ToSqlOutput::from(self.0.clone())) // cloning seems wrong?
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for SyncGuid {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_str().map(|v| SyncGuid(v.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SyncGuid {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
// Typesafe way to manage timestamps.
|
||||
// We should probably work out how to share this too?
|
||||
#[derive(
|
||||
|
|
|
@ -1,57 +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/. */
|
||||
|
||||
/// Returns true for Guids that are valid places guids, and false for all others.
|
||||
pub fn is_valid_places_guid(s: &str) -> bool {
|
||||
s.len() == 12 && s.bytes().all(is_valid_places_byte)
|
||||
}
|
||||
|
||||
/// Returns true if the byte `b` is a valid base64url byte.
|
||||
#[inline]
|
||||
pub fn is_valid_places_byte(b: u8) -> bool {
|
||||
BASE64URL_BYTES[b as usize] == 1
|
||||
}
|
||||
|
||||
// This is used to implement the places tests.
|
||||
const BASE64URL_BYTES: [u8; 256] = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_base64url_bytes() {
|
||||
let mut expect = [0u8; 256];
|
||||
for b in b'0'..=b'9' {
|
||||
expect[b as usize] = 1;
|
||||
}
|
||||
for b in b'a'..=b'z' {
|
||||
expect[b as usize] = 1;
|
||||
}
|
||||
for b in b'A'..=b'Z' {
|
||||
expect[b as usize] = 1;
|
||||
}
|
||||
expect[b'_' as usize] = 1;
|
||||
expect[b'-' as usize] = 1;
|
||||
assert_eq!(&BASE64URL_BYTES[..], &expect[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_for_places() {
|
||||
assert!(is_valid_places_guid("aaaabbbbcccc"));
|
||||
assert!(is_valid_places_guid("09_az-AZ_09-"));
|
||||
assert!(!is_valid_places_guid("aaaabbbbccccd")); // too long
|
||||
assert!(!is_valid_places_guid("aaaabbbbccc")); // too short
|
||||
assert!(!is_valid_places_guid("aaaabbbbccc=")); // right length, bad character
|
||||
}
|
||||
}
|
|
@ -7,13 +7,14 @@ use places::{
|
|||
api::places_api::{ConnectionType, PlacesApi},
|
||||
import::ios_bookmarks::IosBookmarkType,
|
||||
storage::bookmarks,
|
||||
Result, SyncGuid, Timestamp,
|
||||
Result, Timestamp,
|
||||
};
|
||||
use rusqlite::Connection;
|
||||
use sql_support::ConnExt;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use sync_guid::Guid as SyncGuid;
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn empty_ios_db(path: &Path) -> Result<Connection> {
|
||||
|
@ -341,7 +342,7 @@ fn test_import_empty() -> Result<()> {
|
|||
// XXX SyncGuid is a pain to work with, but apparently dogear::Guid can't turn
|
||||
// into it because of our blanket into impl... ;_;
|
||||
fn sync_guid(d: &dogear::Guid) -> SyncGuid {
|
||||
SyncGuid(d.as_str().to_owned())
|
||||
SyncGuid::from(d.as_str())
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -18,7 +18,7 @@ lazy_static = "1.3.0"
|
|||
base64 = "0.10.1"
|
||||
failure = "0.1.5"
|
||||
failure_derive = "0.1.5"
|
||||
log = "0.4.6"
|
||||
log = "0.4.7"
|
||||
rusqlite = { version = "0.19.0", features = ["bundled"] }
|
||||
url = "1.7.2"
|
||||
viaduct = { path = "../viaduct" }
|
||||
|
|
|
@ -15,7 +15,7 @@ default = []
|
|||
force_android = []
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.6"
|
||||
log = "0.4.7"
|
||||
ffi-support = { path = "../support/ffi" }
|
||||
lazy_static = "1.3.0"
|
||||
cfg-if = "0.1.9"
|
||||
|
|
|
@ -8,7 +8,7 @@ license = "MPL-2.0"
|
|||
[dependencies]
|
||||
failure = "0.1.3"
|
||||
fxa-client = { path = "../../fxa-client" }
|
||||
log = "0.4.6"
|
||||
log = "0.4.7"
|
||||
sync15 = { path = "../../sync15" }
|
||||
url = "1.7.1"
|
||||
webbrowser = "0.5.1"
|
||||
|
|
|
@ -135,7 +135,7 @@ impl<'a> FfiStr<'a> {
|
|||
/// If the string should be mandatory, you should use
|
||||
/// [`FfiStr::into_string`] instead. If an owned string is not needed, you
|
||||
/// may want to use [`FfiStr::as_str`] or [`FfiStr::as_opt_str`] instead,
|
||||
/// (however, note the differnces in how invalid UTF-8 is handled, should
|
||||
/// (however, note the differences in how invalid UTF-8 is handled, should
|
||||
/// this be relevant to your use).
|
||||
pub fn into_opt_string(self) -> Option<String> {
|
||||
if !self.cstr.is_null() {
|
||||
|
@ -154,7 +154,7 @@ impl<'a> FfiStr<'a> {
|
|||
/// If the string should *not* be mandatory, you should use
|
||||
/// [`FfiStr::into_opt_string`] instead. If an owned string is not needed,
|
||||
/// you may want to use [`FfiStr::as_str`] or [`FfiStr::as_opt_str`]
|
||||
/// instead, (however, note the differnces in how invalid UTF-8 is handled,
|
||||
/// instead, (however, note the differences in how invalid UTF-8 is handled,
|
||||
/// should this be relevant to your use).
|
||||
#[inline]
|
||||
pub fn into_string(self) -> String {
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "sync-guid"
|
||||
version = "0.1.0"
|
||||
authors = ["Thom Chiovoloni <tchiovoloni@mozilla.com>"]
|
||||
license = "MPL-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
rusqlite = { version = "0.19.0", optional = true }
|
||||
serde = { version = "1.0.79", optional = true }
|
||||
rc_crypto = { path = "../rc_crypto", optional = true }
|
||||
base64 = { version = "0.10.1", optional = true }
|
||||
|
||||
[features]
|
||||
random = ["rc_crypto", "base64"]
|
||||
rusqlite_support = ["rusqlite"]
|
||||
serde_support = ["serde"]
|
||||
# By default we support serde, but not rusqlite.
|
||||
default = ["serde_support"]
|
||||
|
||||
[dev-dependencies]
|
||||
serde_test = "1.0.79"
|
|
@ -0,0 +1,411 @@
|
|||
/* 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/. */
|
||||
|
||||
#![allow(unknown_lints)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
// (It's tempting to avoid the utf8 checks, but they're easy to get wrong, so)
|
||||
#![deny(unsafe_code)]
|
||||
#[cfg(feature = "serde_support")]
|
||||
mod serde_support;
|
||||
|
||||
#[cfg(feature = "rusqlite_support")]
|
||||
mod rusqlite_support;
|
||||
|
||||
use std::{fmt, ops, str};
|
||||
|
||||
/// This is a type intended to be used to represent the guids used by sync. It
|
||||
/// has several benefits over using a `String`:
|
||||
///
|
||||
/// 1. It's more explicit about what is being stored, and could prevent bugs
|
||||
/// where a Guid is passed to a function expecting text.
|
||||
///
|
||||
/// 2. Guids are guaranteed to be immutable.
|
||||
///
|
||||
/// 3. It's optimized for the guids commonly used by sync. In particular, short guids
|
||||
/// (including the guids which would meet `PlacesUtils.isValidGuid`) do not incur
|
||||
/// any heap allocation, and are stored inline.
|
||||
#[derive(Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
pub struct Guid(Repr);
|
||||
|
||||
// The internal representation of a GUID. Most Sync GUIDs are 12 bytes,
|
||||
// and contain only base64url characters; we can store them on the stack
|
||||
// without a heap allocation. However, arbitrary ascii guids of up to length 64
|
||||
// are possible, in which case we fall back to a heap-allocated string.
|
||||
//
|
||||
// This is separate only because making `Guid` an enum would expose the
|
||||
// internals.
|
||||
#[derive(Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
enum Repr {
|
||||
// see FastGuid for invariants
|
||||
Fast(FastGuid),
|
||||
|
||||
// invariants:
|
||||
// - _0.len() > MAX_FAST_GUID_LEN
|
||||
Slow(String),
|
||||
}
|
||||
|
||||
/// Invariants:
|
||||
///
|
||||
/// - `len <= MAX_FAST_GUID_LEN`.
|
||||
/// - `data[0..len]` encodes valid utf8.
|
||||
/// - `data[len..].iter().all(|&b| b == b'\0')`
|
||||
///
|
||||
/// Note: None of these are required for memory safety, just correctness.
|
||||
#[derive(Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
struct FastGuid {
|
||||
len: u8,
|
||||
data: [u8; MAX_FAST_GUID_LEN],
|
||||
}
|
||||
|
||||
// This is the maximum length (experimentally determined) we can make it before
|
||||
// `Repr::Fast` is larger than `Guid::Slow` on 32 bit systems. The important
|
||||
// thing is really that it's not too big, and is above 12 bytes.
|
||||
const MAX_FAST_GUID_LEN: usize = 14;
|
||||
|
||||
impl FastGuid {
|
||||
#[inline]
|
||||
fn from_slice(bytes: &[u8]) -> Self {
|
||||
// Checked by the caller, so debug_assert is fine.
|
||||
debug_assert!(
|
||||
can_use_fast(bytes),
|
||||
"Bug: Caller failed to check can_use_fast: {:?}",
|
||||
bytes
|
||||
);
|
||||
let mut data = [0u8; MAX_FAST_GUID_LEN];
|
||||
data[0..bytes.len()].copy_from_slice(bytes);
|
||||
FastGuid {
|
||||
len: bytes.len() as u8,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn as_str(&self) -> &str {
|
||||
// Note: we only use debug_assert! to enusre valid utf8-ness, so this need
|
||||
str::from_utf8(self.bytes()).expect("Invalid fast guid bytes!")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.len as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bytes(&self) -> &[u8] {
|
||||
&self.data[0..self.len()]
|
||||
}
|
||||
}
|
||||
|
||||
// Returns:
|
||||
// - true to use Repr::Fast
|
||||
// - false to use Repr::Slow
|
||||
#[inline]
|
||||
fn can_use_fast<T: ?Sized + AsRef<[u8]>>(bytes: &T) -> bool {
|
||||
let bytes = bytes.as_ref();
|
||||
// This is fine as a debug_assert since we'll still panic if it's ever used
|
||||
// in such a way where it would matter.
|
||||
debug_assert!(str::from_utf8(bytes).is_ok());
|
||||
bytes.len() <= MAX_FAST_GUID_LEN
|
||||
}
|
||||
|
||||
impl Guid {
|
||||
/// Create a guid from a `str`.
|
||||
#[inline]
|
||||
pub fn new(s: &str) -> Self {
|
||||
Guid::from_slice(s.as_ref())
|
||||
}
|
||||
|
||||
/// Create an empty guid. Usable as a constant.
|
||||
#[inline]
|
||||
pub const fn empty() -> Self {
|
||||
Guid(Repr::Fast(FastGuid {
|
||||
len: 0,
|
||||
data: [0u8; MAX_FAST_GUID_LEN],
|
||||
}))
|
||||
}
|
||||
|
||||
/// Create a random guid (of 12 base64url characters). Requires the `random`
|
||||
/// feature.
|
||||
#[cfg(feature = "random")]
|
||||
pub fn random() -> Self {
|
||||
let mut bytes = [0u8; 9];
|
||||
// TODO: investigate if we should replace this with something infallible
|
||||
// (from e.g. `rand`)
|
||||
rc_crypto::rand::fill(&mut bytes).unwrap();
|
||||
|
||||
// Note: only first 12 bytes are used, but remaining are required to
|
||||
// build the FastGuid
|
||||
let mut output = [0u8; MAX_FAST_GUID_LEN];
|
||||
|
||||
let bytes_written =
|
||||
base64::encode_config_slice(&bytes, base64::URL_SAFE_NO_PAD, &mut output[..12]);
|
||||
|
||||
debug_assert!(bytes_written == 12);
|
||||
|
||||
Guid(Repr::Fast(FastGuid {
|
||||
len: 12,
|
||||
data: output,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Convert `b` into a `Guid`.
|
||||
#[inline]
|
||||
pub fn from_string(s: String) -> Self {
|
||||
Guid::from_vec(s.into_bytes())
|
||||
}
|
||||
|
||||
/// Convert `b` into a `Guid`.
|
||||
#[inline]
|
||||
pub fn from_slice(b: &[u8]) -> Self {
|
||||
if can_use_fast(b) {
|
||||
Guid(Repr::Fast(FastGuid::from_slice(b)))
|
||||
} else {
|
||||
Guid::new_slow(b.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert `v` to a `Guid`, consuming it.
|
||||
#[inline]
|
||||
pub fn from_vec(v: Vec<u8>) -> Self {
|
||||
if can_use_fast(&v) {
|
||||
Guid(Repr::Fast(FastGuid::from_slice(&v)))
|
||||
} else {
|
||||
Guid::new_slow(v)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the data backing this `Guid` as a `&[u8]`.
|
||||
#[inline]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
match &self.0 {
|
||||
Repr::Fast(rep) => rep.bytes(),
|
||||
Repr::Slow(rep) => rep.as_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the data backing this `Guid` as a `&str`.
|
||||
#[inline]
|
||||
pub fn as_str(&self) -> &str {
|
||||
match &self.0 {
|
||||
Repr::Fast(rep) => rep.as_str(),
|
||||
Repr::Slow(rep) => rep.as_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this `Guid` into a `String`, consuming it in the process.
|
||||
#[inline]
|
||||
pub fn into_string(self) -> String {
|
||||
match self.0 {
|
||||
Repr::Fast(rep) => rep.as_str().into(),
|
||||
Repr::Slow(rep) => rep,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true for Guids that are valid places guids, and false for all others.
|
||||
pub fn is_valid_for_places(&self) -> bool {
|
||||
self.len() == 12 && self.bytes().all(Guid::is_valid_places_byte)
|
||||
}
|
||||
|
||||
/// Returns true if the byte `b` is a valid base64url byte.
|
||||
#[inline]
|
||||
pub fn is_valid_places_byte(b: u8) -> bool {
|
||||
BASE64URL_BYTES[b as usize] == 1
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn new_slow(v: Vec<u8>) -> Self {
|
||||
assert!(
|
||||
!can_use_fast(&v),
|
||||
"Could use fast for guid (len = {})",
|
||||
v.len()
|
||||
);
|
||||
Guid(Repr::Slow(
|
||||
String::from_utf8(v).expect("Invalid slow guid bytes!"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// This is used to implement the places tests.
|
||||
const BASE64URL_BYTES: [u8; 256] = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
];
|
||||
|
||||
impl<'a> From<&'a str> for Guid {
|
||||
#[inline]
|
||||
fn from(s: &'a str) -> Guid {
|
||||
Guid::from_slice(s.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [u8]> for Guid {
|
||||
#[inline]
|
||||
fn from(s: &'a [u8]) -> Guid {
|
||||
Guid::from_slice(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Guid {
|
||||
#[inline]
|
||||
fn from(s: String) -> Guid {
|
||||
Guid::from_string(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for Guid {
|
||||
#[inline]
|
||||
fn from(v: Vec<u8>) -> Guid {
|
||||
Guid::from_vec(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Guid> for String {
|
||||
#[inline]
|
||||
fn from(guid: Guid) -> String {
|
||||
guid.into_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Guid> for Vec<u8> {
|
||||
#[inline]
|
||||
fn from(guid: Guid) -> Vec<u8> {
|
||||
guid.into_string().into_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for Guid {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for Guid {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for Guid {
|
||||
type Target = str;
|
||||
#[inline]
|
||||
fn deref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
// The default Debug impl is pretty unhelpful here.
|
||||
impl fmt::Debug for Guid {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Guid({:?})", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Guid {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(self.as_str(), f)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_guid_eq {
|
||||
($($other: ty),+) => {$(
|
||||
impl<'a> PartialEq<$other> for Guid {
|
||||
#[inline]
|
||||
fn eq(&self, other: &$other) -> bool {
|
||||
PartialEq::eq(AsRef::<[u8]>::as_ref(self), AsRef::<[u8]>::as_ref(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq<Guid> for $other {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Guid) -> bool {
|
||||
PartialEq::eq(AsRef::<[u8]>::as_ref(self), AsRef::<[u8]>::as_ref(other))
|
||||
}
|
||||
}
|
||||
)+}
|
||||
}
|
||||
|
||||
// Implement direct comparison with some common types from the stdlib.
|
||||
impl_guid_eq![str, &'a str, String, [u8], &'a [u8], Vec<u8>];
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_base64url_bytes() {
|
||||
let mut expect = [0u8; 256];
|
||||
for b in b'0'..=b'9' {
|
||||
expect[b as usize] = 1;
|
||||
}
|
||||
for b in b'a'..=b'z' {
|
||||
expect[b as usize] = 1;
|
||||
}
|
||||
for b in b'A'..=b'Z' {
|
||||
expect[b as usize] = 1;
|
||||
}
|
||||
expect[b'_' as usize] = 1;
|
||||
expect[b'-' as usize] = 1;
|
||||
assert_eq!(&BASE64URL_BYTES[..], &expect[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_for_places() {
|
||||
assert!(Guid::from("aaaabbbbcccc").is_valid_for_places());
|
||||
assert!(Guid::from_slice(b"09_az-AZ_09-").is_valid_for_places());
|
||||
assert!(!Guid::from("aaaabbbbccccd").is_valid_for_places()); // too long
|
||||
assert!(!Guid::from("aaaabbbbccc").is_valid_for_places()); // too short
|
||||
assert!(!Guid::from("aaaabbbbccc=").is_valid_for_places()); // right length, bad character
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comparison() {
|
||||
assert_eq!(Guid::from("abcdabcdabcd"), "abcdabcdabcd");
|
||||
assert_ne!(Guid::from("abcdabcdabcd".to_string()), "ABCDabcdabcd");
|
||||
|
||||
assert_eq!(Guid::from("abcdabcdabcd"), &b"abcdabcdabcd"[..]); // b"abcdabcdabcd" has type &[u8; 12]...
|
||||
assert_ne!(Guid::from(&b"abcdabcdabcd"[..]), &b"ABCDabcdabcd"[..]);
|
||||
|
||||
assert_eq!(
|
||||
Guid::from(b"abcdabcdabcd"[..].to_owned()),
|
||||
"abcdabcdabcd".to_string()
|
||||
);
|
||||
assert_ne!(Guid::from("abcdabcdabcd"), "ABCDabcdabcd".to_string());
|
||||
|
||||
assert_eq!(
|
||||
Guid::from("abcdabcdabcd1234"),
|
||||
Vec::from(b"abcdabcdabcd1234".as_ref())
|
||||
);
|
||||
assert_ne!(
|
||||
Guid::from("abcdabcdabcd4321"),
|
||||
Vec::from(b"ABCDabcdabcd4321".as_ref())
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "random")]
|
||||
#[test]
|
||||
fn test_random() {
|
||||
use std::collections::HashSet;
|
||||
// Used to verify uniqueness within our sample of 1000. Could cause
|
||||
// random failures, but desktop has the same test, and it's never caused
|
||||
// a problem AFAIK.
|
||||
let mut seen: HashSet<String> = HashSet::new();
|
||||
for _ in 0..1000 {
|
||||
let g = Guid::random();
|
||||
assert_eq!(g.len(), 12);
|
||||
assert!(g.is_valid_for_places());
|
||||
let decoded = base64::decode_config(&g, base64::URL_SAFE_NO_PAD).unwrap();
|
||||
assert_eq!(decoded.len(), 9);
|
||||
let no_collision = seen.insert(g.clone().into_string());
|
||||
assert!(no_collision, "{}", g);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/* 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/. */
|
||||
|
||||
#![cfg(feature = "rusqlite_support")]
|
||||
|
||||
use crate::Guid;
|
||||
use rusqlite::{
|
||||
self,
|
||||
types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef},
|
||||
};
|
||||
|
||||
impl ToSql for Guid {
|
||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
||||
Ok(ToSqlOutput::from(self.as_str()))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for Guid {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value.as_str().map(Guid::from)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/* 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/. */
|
||||
|
||||
#![cfg(feature = "serde_support")]
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use serde::{
|
||||
de::{self, Deserialize, Deserializer, Visitor},
|
||||
ser::{Serialize, Serializer},
|
||||
};
|
||||
|
||||
use crate::Guid;
|
||||
|
||||
struct GuidVisitor;
|
||||
impl<'de> Visitor<'de> for GuidVisitor {
|
||||
type Value = Guid;
|
||||
#[inline]
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a sync guid")
|
||||
}
|
||||
#[inline]
|
||||
fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
|
||||
Ok(Guid::from_slice(s.as_ref()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Guid {
|
||||
#[inline]
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str(GuidVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Guid {
|
||||
#[inline]
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use serde_test::{assert_tokens, Token};
|
||||
#[test]
|
||||
fn test_ser_de() {
|
||||
let guid = Guid::from("asdffdsa12344321");
|
||||
assert_tokens(&guid, &[Token::Str("asdffdsa12344321")]);
|
||||
|
||||
let guid = Guid::from("");
|
||||
assert_tokens(&guid, &[Token::Str("")]);
|
||||
|
||||
let guid = Guid::from(&b"abcd43211234"[..]);
|
||||
assert_tokens(&guid, &[Token::Str("abcd43211234")]);
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ license = "MPL-2.0"
|
|||
crate-type = ["lib"]
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.49.0"
|
||||
bindgen = "0.50.0"
|
||||
serde = "1.0.91"
|
||||
serde_derive = "1.0.91"
|
||||
toml = "0.5.0"
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package mozilla.appservices.sync15
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.json.JSONException
|
||||
|
||||
|
|
|
@ -40,7 +40,6 @@ a rc file or similar so they persist between reboots etc.
|
|||
2. Install `rustup` from https://rustup.rs:
|
||||
- If you already have it, run `rustup update`
|
||||
- Run `rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android`
|
||||
- Run `rustup toolchain add beta` (TODO: this no longer appears necessary).
|
||||
|
||||
3. Ensure your clone of `mozilla/application-services` is up to date.
|
||||
|
||||
|
@ -64,7 +63,8 @@ a rc file or similar so they persist between reboots etc.
|
|||
them somewhere - use the "SDK Manager" to identify this location.
|
||||
- Set `ANDROID_HOME` to this location and add it to your rc file.
|
||||
|
||||
7. Build openssl and sqlcipher
|
||||
7. Build NSS, OpenSSL and sqlcipher
|
||||
- Make sure both [GYP](https://github.com/mogemimi/pomdog/wiki/How-to-Install-GYP) and [ninja](https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages) are available on your system.
|
||||
- `cd path/to/application-services/libs` (Same dir you were just in for step 4)
|
||||
- `./build-all.sh android` (Go make some coffee or something, this will take
|
||||
some time as it has to compile sqlcipher and openssl for x86, x86_64, arm, and arm64).
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
## libs
|
||||
|
||||
This directory builds `openssl` for iOS, Android and desktop platforms.
|
||||
This directory builds the required libraries for iOS, Android and desktop platforms.
|
||||
|
||||
### Usage
|
||||
|
||||
|
@ -8,6 +8,7 @@ This directory builds `openssl` for iOS, Android and desktop platforms.
|
|||
* `./build-all.sh android` - Build for Android
|
||||
* `./build-all.sh desktop` - Build for Desktop
|
||||
|
||||
[GYP](https://github.com/mogemimi/pomdog/wiki/How-to-Install-GYP) and [ninja](https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages) are required to run these scripts.
|
||||
|
||||
### Supported architectures
|
||||
|
||||
|
|
|
@ -7,25 +7,26 @@
|
|||
|
||||
if [ ! -f "$(pwd)/libs/build-all.sh" ]; then
|
||||
echo "ERROR: bootstrap-desktop.sh should be run from the root directory of the repo"
|
||||
else
|
||||
if [ "$(uname -s)" == "Darwin" ]; then
|
||||
APPSERVICES_PLATFORM_DIR="$(pwd)/libs/desktop/darwin"
|
||||
else
|
||||
APPSERVICES_PLATFORM_DIR="$(pwd)/libs/desktop/linux-x86-64"
|
||||
fi
|
||||
export SQLCIPHER_LIB_DIR="${APPSERVICES_PLATFORM_DIR}/sqlcipher/lib"
|
||||
export SQLCIPHER_INCLUDE_DIR="${APPSERVICES_PLATFORM_DIR}/sqlcipher/include"
|
||||
export OPENSSL_DIR="${APPSERVICES_PLATFORM_DIR}/openssl"
|
||||
export NSS_DIR="${APPSERVICES_PLATFORM_DIR}/nss"
|
||||
if [ ! -d "${SQLCIPHER_LIB_DIR}" ] || [ ! -d "${OPENSSL_DIR}" ] || [ ! -d "${NSS_DIR}" ]; then
|
||||
pushd libs
|
||||
./build-all.sh desktop
|
||||
popd
|
||||
fi;
|
||||
if [ "$(uname -s)" == "Darwin" ] && [ ! -f "/usr/include/pthread.h" ]; then
|
||||
# rustc does not include the macOS SDK headers in its include list yet
|
||||
# (see https://developer.apple.com/documentation/xcode_release_notes/xcode_10_release_notes)
|
||||
echo "macOS system headers are not installed in /usr/include, please run:"
|
||||
echo "open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(uname -s)" == "Darwin" ]; then
|
||||
APPSERVICES_PLATFORM_DIR="$(pwd)/libs/desktop/darwin"
|
||||
else
|
||||
APPSERVICES_PLATFORM_DIR="$(pwd)/libs/desktop/linux-x86-64"
|
||||
fi
|
||||
export SQLCIPHER_LIB_DIR="${APPSERVICES_PLATFORM_DIR}/sqlcipher/lib"
|
||||
export SQLCIPHER_INCLUDE_DIR="${APPSERVICES_PLATFORM_DIR}/sqlcipher/include"
|
||||
export OPENSSL_DIR="${APPSERVICES_PLATFORM_DIR}/openssl"
|
||||
export NSS_DIR="${APPSERVICES_PLATFORM_DIR}/nss"
|
||||
if [ ! -d "${SQLCIPHER_LIB_DIR}" ] || [ ! -d "${OPENSSL_DIR}" ] || [ ! -d "${NSS_DIR}" ]; then
|
||||
pushd libs
|
||||
./build-all.sh desktop
|
||||
popd
|
||||
fi;
|
||||
if [ "$(uname -s)" == "Darwin" ] && [ ! -f "/usr/include/pthread.h" ]; then
|
||||
# rustc does not include the macOS SDK headers in its include list yet
|
||||
# (see https://developer.apple.com/documentation/xcode_release_notes/xcode_10_release_notes)
|
||||
echo "macOS system headers are not installed in /usr/include, please run:"
|
||||
echo "open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg"
|
||||
fi
|
||||
|
|
|
@ -33,6 +33,16 @@ PLATFORM="${1}"
|
|||
abspath () { case "${1}" in /*)printf "%s\\n" "${1}";; *)printf "%s\\n" "${PWD}/${1}";; esac; }
|
||||
export -f abspath
|
||||
|
||||
if ! [ -x "$(command -v gyp)" ]; then
|
||||
echo 'Error: gyp needs to be installed and executable. See https://github.com/mogemimi/pomdog/wiki/How-to-Install-GYP for install instructions.' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -x "$(command -v ninja)" ]; then
|
||||
echo 'Error: ninja needs to be installed and executable. See https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages for install instructions.' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OPENSSL="openssl-${OPENSSL_VERSION}"
|
||||
rm -rf "${OPENSSL}"
|
||||
if [ ! -e "${OPENSSL}.tar.gz" ]; then
|
||||
|
|
|
@ -33,7 +33,6 @@ android {
|
|||
afterEvaluate {
|
||||
android.sourceSets.debug.jniLibs.srcDirs = android.sourceSets.main.jniLibs.srcDirs
|
||||
android.sourceSets.release.jniLibs.srcDirs = android.sourceSets.main.jniLibs.srcDirs
|
||||
// android.sourceSets.main.jniLibs.srcDirs = []
|
||||
}
|
||||
|
||||
configurations {
|
||||
|
|
|
@ -11,7 +11,7 @@ sync15 = { path = "../../components/sync15", features = ["reqwest"] }
|
|||
fxa-client = { path = "../../components/fxa-client", features = ["reqwest"] }
|
||||
url = "1.7.1"
|
||||
env_logger = "0.6.2"
|
||||
log = "0.4.6"
|
||||
log = "0.4.7"
|
||||
failure = "0.1.3"
|
||||
rand = "0.7.0"
|
||||
lazy_static = "1.3.0"
|
||||
|
|
Загрузка…
Ссылка в новой задаче