Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
eoger committed Nov 12, 2018
1 parent 70de778 commit 42b3239
Show file tree
Hide file tree
Showing 7 changed files with 374 additions and 13 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -6,6 +6,7 @@ members = [
"sync15-adapter",
"logins-sql",
"logins-sql/ffi",
"sendtab-proto",
"components/places",
"components/places/ffi",
"components/support/sql",
Expand Down
3 changes: 3 additions & 0 deletions fxa-client/src/errors.rs
Expand Up @@ -79,6 +79,9 @@ pub enum ErrorKind {
#[fail(display = "No cached token for scope {}", _0)]
NoCachedToken(&'static str),

#[fail(display = "No cached refresh token")]
NoRefreshToken,

#[fail(display = "Could not find a refresh token in the server response")]
RefreshTokenNotPresent,

Expand Down
120 changes: 120 additions & 0 deletions fxa-client/src/http_client/mod.rs
Expand Up @@ -10,6 +10,7 @@ use reqwest::{header, Client as ReqwestClient, Method, Request, Response, Status
use ring::{digest, hkdf, hmac};
use serde_json;
use std;
use std::collections::HashMap;
#[cfg(feature = "browserid")]
use util::Xorable;

Expand Down Expand Up @@ -259,6 +260,72 @@ impl<'a> Client<'a> {
Ok(())
}

pub fn pending_commands(&self, refresh_token: &str) -> Result<PendingCommands> {
let url = self.config.auth_url_path("v1/client_instance/pending_commands")?;
let client = ReqwestClient::new();
let request = client
.request(Method::GET, url)
.header(header::AUTHORIZATION, format!("Bearer {}", refresh_token))
.build()?;
Client::make_request(request)?.json().map_err(|e| e.into())
}

pub fn invoke_command(&self, token: &str, command: &str, target: &str, payload: &serde_json::Value) -> Result<()> {
let body = json!({
"command": command,
"target": target,
"payload": payload
});
let url = self.config.auth_url_path("v1/clients_instances/invoke_command")?;
let client = ReqwestClient::new();
let request = client
.request(Method::POST, url)
.header(header::AUTHORIZATION, format!("Bearer {}", token))
.header(header::CONTENT_TYPE, "application/json")
.body(body.to_string())
.build()?;
Client::make_request(request)?;
Ok(())
}

pub fn clients_instances(&self, token: &str) -> Result<Vec<ClientInstance>> {
let url = self.config.auth_url_path("v1/clients_instances")?;
let client = ReqwestClient::new();
let request = client
.request(Method::GET, url)
.header(header::AUTHORIZATION, format!("Bearer {}", token))
.build()?;
Client::make_request(request)?.json().map_err(|e| e.into())
}

pub fn upsert_client_instance(&self, refresh_token: &str, metadata: ClientInstanceRequest) -> Result<()> {
let body = serde_json::to_string(&metadata)?;
let url = self.config.auth_url_path("v1/client_instance")?;
let client = ReqwestClient::new();
let request = client
.request(Method::POST, url)
.header(header::AUTHORIZATION, format!("Bearer {}", refresh_token))
.header(header::CONTENT_TYPE, "application/json")
.body(body)
.build()?;
Client::make_request(request)?;
Ok(())
}

pub fn patch_client_instance_commands(&self, refresh_token: &str, commands: HashMap<String, Option<String>>) -> Result<()> {
let body = serde_json::to_string(&commands)?;
let url = self.config.auth_url_path("v1/client_instance/commands")?;
let client = ReqwestClient::new();
let request = client
.request(Method::PATCH, url)
.header(header::AUTHORIZATION, format!("Bearer {}", refresh_token))
.header(header::CONTENT_TYPE, "application/json")
.body(body)
.build()?;
Client::make_request(request)?;
Ok(())
}

#[cfg(feature = "browserid")]
pub fn sign(&self, session_token: &[u8], key_pair: &BrowserIDKeyPair) -> Result<SignResponse> {
let public_key_json = key_pair.to_json(false)?;
Expand Down Expand Up @@ -333,6 +400,59 @@ pub struct ResponseAndETag<T> {
pub etag: Option<String>,
}

#[derive(Deserialize)]
pub struct PendingCommands {
pub index: u64,
pub last: Option<bool>,
pub messages: Vec<PendingCommand>,
}

#[derive(Deserialize)]
pub struct PendingCommand {
pub index: u64,
pub data: CommandData,
}

#[derive(Deserialize)]
pub struct CommandData {
pub command: String,
pub payload: serde_json::Value,
pub sender: Option<String>,
}

#[derive(Deserialize, Serialize)]
pub struct PushSubscription {
#[serde(rename = "pushEndpoint")]
pub endpoint: String,
#[serde(rename = "pushPublicKey")]
pub public_key: String,
#[serde(rename = "pushAuthKey")]
pub auth_key: String,
}

#[derive(Serialize)]
pub struct ClientInstanceRequest {
#[serde(skip_serializing_if="Option::is_none")]
pub name: Option<String>,
#[serde(flatten)]
pub push_subscription: Option<PushSubscription>,
#[serde(skip_serializing_if="Option::is_none")]
#[serde(rename = "availableCommands")]
pub available_commands: Option<HashMap<String, String>>,
}

#[derive(Deserialize)]
pub struct ClientInstance {
pub id: String,
#[serde(rename = "clientId")]
pub client_id: String,
pub name: Option<String>,
#[serde(flatten)]
pub push_subscription: Option<PushSubscription>,
#[serde(rename = "availableCommands")]
pub available_commands: Option<HashMap<String, String>>,
}

#[derive(Deserialize)]
pub struct LoginResponse {
pub uid: String,
Expand Down
79 changes: 66 additions & 13 deletions fxa-client/src/lib.rs
Expand Up @@ -45,7 +45,7 @@ use self::login_sm::*;
use errors::*;
#[cfg(feature = "browserid")]
use http_client::browser_id::jwt_utils;
use http_client::{Client, ProfileResponse};
use http_client::{Client, ClientInstance, ClientInstanceRequest, PendingCommands, PushSubscription, ProfileResponse};
use ring::digest;
use ring::rand::{SecureRandom, SystemRandom};
use scoped_keys::ScopedKeysFlow;
Expand Down Expand Up @@ -408,6 +408,24 @@ impl FirefoxAccount {
Ok(())
}

pub fn pending_commands(&self) -> Result<PendingCommands> {
let refresh_token = match self.state.refresh_token {
Some(ref token) => &token.id,
None => return Err(ErrorKind::NoRefreshToken.into()),
};
let client = Client::new(&self.state.config);
client.pending_commands(refresh_token)
}

pub fn invoke_command(&mut self, command: &str, target_device_id: &str, payload: &serde_json::Value) -> Result<()> {
let access_token = match self.get_oauth_token("commands:write")? {
Some(token) => token.access_token,
None => return Err(ErrorKind::NoCachedToken("commands:write").into()),
};
let client = Client::new(&self.state.config);
client.invoke_command(&access_token, command, target_device_id, payload)
}

fn random_base64_url_string(len: usize) -> Result<String> {
let mut out = vec![0u8; len];
RNG.fill(&mut out).map_err(|_| ErrorKind::RngFailure)?;
Expand Down Expand Up @@ -492,24 +510,59 @@ impl FirefoxAccount {
self.state.config.token_server_endpoint_url()
}

pub fn handle_push_message(&self) {
panic!("Not implemented yet!")
}

pub fn register_device(&self) {
panic!("Not implemented yet!")
pub fn clients_instances(&mut self) -> Result<Vec<ClientInstance>> {
let access_token = match self.get_oauth_token("clients:read")? {
Some(token) => token.access_token,
None => return Err(ErrorKind::NoCachedToken("clients:read").into()),
};
let client = Client::new(&self.state.config);
client.clients_instances(&access_token)
}

pub fn get_devices_list(&self) {
panic!("Not implemented yet!")
pub fn set_push_subscription(&self, endpoint: &str, public_key: &str, auth_key: &str) -> Result<()> {
let refresh_token = match self.state.refresh_token {
Some(ref token) => &token.id,
None => return Err(ErrorKind::NoRefreshToken.into()),
};
let metadata = ClientInstanceRequest {
name: None,
push_subscription: Some(PushSubscription {
endpoint: endpoint.to_string(),
public_key: public_key.to_string(),
auth_key: auth_key.to_string(),
}),
available_commands: None,
};
let client = Client::new(&self.state.config);
client.upsert_client_instance(refresh_token, metadata)?;
Ok(())
}

pub fn send_message(&self) {
panic!("Not implemented yet!")
pub fn set_name(&self, name: &str) -> Result<()> {
let refresh_token = match self.state.refresh_token {
Some(ref token) => &token.id,
None => return Err(ErrorKind::NoRefreshToken.into()),
};
let metadata = ClientInstanceRequest {
name: Some(name.to_string()),
push_subscription: None,
available_commands: None,
};
let client = Client::new(&self.state.config);
client.upsert_client_instance(refresh_token, metadata)?;
Ok(())
}

pub fn retrieve_messages(&self) {
panic!("Not implemented yet!")
pub fn register_command(&self, command: &str, value: &str) -> Result<()> {
let refresh_token = match self.state.refresh_token {
Some(ref token) => &token.id,
None => return Err(ErrorKind::NoRefreshToken.into()),
};
let mut commands: HashMap<String, Option<String>> = HashMap::new();
commands.insert(command.to_string(), Some(value.to_string()));
let client = Client::new(&self.state.config);
client.patch_client_instance_commands(refresh_token, commands)?;
Ok(())
}

pub fn register_persist_callback(&mut self, persist_callback: PersistCallback) {
Expand Down
18 changes: 18 additions & 0 deletions sendtab-proto/Cargo.toml
@@ -0,0 +1,18 @@
[package]
name = "sendtab-proto"
version = "0.1.0"
authors = ["Edouard Oger <eoger@fastmail.com>"]

[dependencies.fxa-client]
path = "../fxa-client"

[dependencies]
base64 = "0.9.3"
dialoguer = "0.1.0"
text_io = "0.1.7"
sync15-adapter = { path = "../sync15-adapter" }
url = "1.7.1"
ece = {git = "https://github.com/eoger/rust-ece"}
hex = "0.3.2"
serde = "1.0"
serde_json = "1.0"
4 changes: 4 additions & 0 deletions sendtab-proto/README.md
@@ -0,0 +1,4 @@
# Sanvich Desktop

Run with `cargo run`, open the link provided in your browser and copy-paste
the redirect URL back to sandvich.

0 comments on commit 42b3239

Please sign in to comment.