-
Notifications
You must be signed in to change notification settings - Fork 230
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merge sync15 adapter #12
Changes from all commits
f826805
d75ed6c
f9d5436
6de7d68
bafddda
0461cda
88ebe67
18cc6de
ebfa93a
b22952b
fec1cf5
4ec9483
c049ccc
9f1888e
e77fb59
584a869
0b148c5
8b12dc4
3ad7ef4
c0a1c41
7ca3dca
a73408d
fa977ef
d4ef087
fc4a5e9
6356639
88da475
bcbb71c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
website/build | ||
target/ | ||
target | ||
Cargo.lock | ||
credentials.json |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
[workspace] | ||
members = [ | ||
"boxlocker", | ||
"fxa-rust-client" | ||
"sync15-adapter", | ||
"sync15-adapter/ffi" | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
[package] | ||
name = "sync15-adapter" | ||
version = "0.1.0" | ||
authors = ["Thom Chiovoloni <[email protected]>"] | ||
|
||
[dependencies] | ||
base64 = "0.9.0" | ||
serde = "1.0" | ||
serde_derive = "1.0" | ||
serde_json = "1.0" | ||
url = "1.6.0" | ||
reqwest = "0.8.2" | ||
error-chain = "0.11" | ||
openssl = "0.10.7" | ||
hawk = { git = "https://github.com/eoger/rust-hawk", branch = "use-openssl" } | ||
hyper = "0.11" | ||
log = "0.4" | ||
lazy_static = "1.0" | ||
|
||
[dev-dependencies] | ||
env_logger = "0.5" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
|
||
extern crate sync15_adapter as sync; | ||
extern crate error_chain; | ||
extern crate url; | ||
extern crate base64; | ||
extern crate reqwest; | ||
|
||
extern crate serde; | ||
#[macro_use] | ||
extern crate serde_derive; | ||
extern crate serde_json; | ||
|
||
extern crate env_logger; | ||
|
||
use std::io::{self, Read, Write}; | ||
use std::error::Error; | ||
use std::fs; | ||
use std::process; | ||
use std::time; | ||
use std::collections::HashMap; | ||
use std::time::{SystemTime, UNIX_EPOCH}; | ||
|
||
#[derive(Debug, Deserialize)] | ||
struct OAuthCredentials { | ||
access_token: String, | ||
refresh_token: String, | ||
keys: HashMap<String, ScopedKeyData>, | ||
expires_in: u64, | ||
auth_at: u64, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
struct ScopedKeyData { | ||
k: String, | ||
kid: String, | ||
scope: String, | ||
} | ||
|
||
fn do_auth(recur: bool) -> Result<OAuthCredentials, Box<Error>> { | ||
match fs::File::open("./credentials.json") { | ||
Err(_) => { | ||
if recur { | ||
panic!("Failed to open credentials 2nd time"); | ||
} | ||
println!("No credentials found, invoking boxlocker.py..."); | ||
process::Command::new("python") | ||
.arg("../boxlocker/boxlocker.py").output() | ||
.expect("Failed to run boxlocker.py"); | ||
return do_auth(true); | ||
}, | ||
Ok(mut file) => { | ||
let mut s = String::new(); | ||
file.read_to_string(&mut s)?; | ||
let creds: OAuthCredentials = serde_json::from_str(&s)?; | ||
let time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); | ||
if creds.expires_in + creds.auth_at < time { | ||
println!("Warning, credentials may be stale."); | ||
} | ||
Ok(creds) | ||
} | ||
} | ||
} | ||
|
||
fn prompt_string<S: AsRef<str>>(prompt: S) -> Option<String> { | ||
print!("{}: ", prompt.as_ref()); | ||
let _ = io::stdout().flush(); // Don't care if flush fails really. | ||
let mut s = String::new(); | ||
io::stdin().read_line(&mut s).expect("Failed to read line..."); | ||
if let Some('\n') = s.chars().next_back() { s.pop(); } | ||
if let Some('\r') = s.chars().next_back() { s.pop(); } | ||
if s.len() == 0 { | ||
None | ||
} else { | ||
Some(s) | ||
} | ||
} | ||
|
||
fn read_login() -> sync::record_types::PasswordRecord { | ||
let username = prompt_string("username").unwrap_or(String::new()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
let password = prompt_string("password").unwrap_or(String::new()); | ||
let form_submit_url = prompt_string("form_submit_url"); | ||
let hostname = prompt_string("hostname"); | ||
let http_realm = prompt_string("http_realm"); | ||
let username_field = prompt_string("username_field").unwrap_or(String::new()); | ||
let password_field = prompt_string("password_field").unwrap_or(String::new()); | ||
let since_unix_epoch = time::SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap(); | ||
let dur_ms = since_unix_epoch.as_secs() * 1000 + ((since_unix_epoch.subsec_nanos() / 1_000_000) as u64); | ||
let ms_i64 = dur_ms as i64; | ||
sync::record_types::PasswordRecord { | ||
id: sync::util::random_guid().unwrap(), | ||
username, | ||
password, | ||
username_field, | ||
password_field, | ||
form_submit_url, | ||
http_realm, | ||
hostname, | ||
time_created: ms_i64, | ||
time_password_changed: ms_i64, | ||
times_used: None, | ||
time_last_used: Some(ms_i64), | ||
} | ||
} | ||
|
||
fn prompt_bool(msg: &str) -> Option<bool> { | ||
let result = prompt_string(msg); | ||
result.and_then(|r| match r.chars().next().unwrap() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Could you spell this |
||
'y' | 'Y' | 't' | 'T' => Some(true), | ||
'n' | 'N' | 'f' | 'F' => Some(false), | ||
_ => None | ||
}) | ||
} | ||
|
||
fn prompt_chars(msg: &str) -> Option<char> { | ||
prompt_string(msg).and_then(|r| r.chars().next()) | ||
} | ||
|
||
fn start() -> Result<(), Box<Error>> { | ||
let oauth_data = do_auth(false)?; | ||
|
||
let scope = &oauth_data.keys["https://identity.mozilla.com/apps/oldsync"]; | ||
|
||
let mut svc = sync::Sync15Service::new( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think your intuition that Some of it could be managed by an HTTP client wrapper, some by a collections manager, and some by a collection that handles version and sync ID changes, and persistence. (Maybe the collection is also the right place to plug in storage? It would be a merger of the Consumers would create these object, hold on to some state, and pass it around. So the app would create the key bundle, Sync client (which maybe manages its own In short: let's break up what we have into smaller pieces, try to write an end-to-end consumer, and figure out how to reassemble them in a way that makes sense. |
||
sync::Sync15ServiceInit { | ||
key_id: scope.kid.clone(), | ||
sync_key: scope.k.clone(), | ||
access_token: oauth_data.access_token.clone(), | ||
tokenserver_base_url: "https://oauth-sync.dev.lcip.org/syncserver/token".into(), | ||
} | ||
)?; | ||
|
||
svc.remote_setup()?; | ||
let passwords = svc.all_records::<sync::record_types::PasswordRecord>("passwords")? | ||
.into_iter() | ||
.filter_map(|r| r.record()) | ||
.collect::<Vec<_>>(); | ||
|
||
println!("Found {} passwords", passwords.len()); | ||
|
||
for pw in passwords.iter() { | ||
println!("{:?}", pw.payload); | ||
} | ||
|
||
if !prompt_bool("Would you like to make changes? [y/N]").unwrap_or(false) { | ||
return Ok(()); | ||
} | ||
|
||
let mut ids: Vec<String> = passwords.iter().map(|p| p.id.clone()).collect(); | ||
|
||
let mut upd = sync::CollectionUpdate::new(&svc, false); | ||
loop { | ||
match prompt_chars("Add, delete, or commit [adc]:").unwrap_or('s') { | ||
'A' | 'a' => { | ||
let record = read_login(); | ||
upd.add_record(record); | ||
}, | ||
'D' | 'd' => { | ||
for (i, id) in ids.iter().enumerate() { | ||
println!("{}: {}", i, id); | ||
} | ||
if let Some(index) = prompt_string("Index to delete (enter index)").and_then(|x| x.parse::<usize>().ok()) { | ||
let result = ids.swap_remove(index); | ||
upd.add_tombstone(result); | ||
} else { | ||
println!("???"); | ||
} | ||
}, | ||
'C' | 'c' => { | ||
println!("committing!"); | ||
let (good, bad) = upd.upload()?; | ||
println!("Uploded {} ids successfully, and {} unsuccessfully", | ||
good.len(), bad.len()); | ||
break; | ||
}, | ||
c => { | ||
println!("Unknown action '{}', exiting.", c); | ||
break; | ||
} | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
fn main() { | ||
env_logger::init(); | ||
start().unwrap(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[package] | ||
name = "sync-adapter-ffi" | ||
version = "0.1.0" | ||
authors = ["Thom Chiovoloni <[email protected]>"] | ||
|
||
[lib] | ||
name = "sync_adapter" | ||
crate-type = ["staticlib"] | ||
|
||
[dependencies] | ||
libc = "0.2" | ||
|
||
[dependencies.sync15-adapter] | ||
path = "../" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Sync 1.5 Client FFI | ||
|
||
This README is shamelessly stolen from the one in the fxa client directory | ||
|
||
## iOS build | ||
|
||
- Make sure you have the nightly compiler in order to get LLVM Bitcode generation. | ||
- Install [cargo-lipo](https://github.com/TimNN/cargo-lipo/#installation). | ||
- Build with: `OPENSSL_DIR=/usr/local/opt/openssl cargo +nightly lipo --release` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using trim_matches seems like it would be clearer, although presumably requiring additional allocation of string objects. For my education, is it written this way for efficiency or for some other reason?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, I wouldn't worry about allocations as a result of user input which should be rare. I just saw that https://doc.rust-lang.org/std/str/pattern/trait.Pattern.html was not stable, and got confused as to whether or not
trim_matches
was usable.