Skip to content
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

bug: store meta-data for push component in the database. #907

Merged
merged 3 commits into from
Apr 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

[Full Changelog](https://github.com/mozilla/application-services/compare/v0.24.0...master)

## Push

### What's fixed

- PushAPI now stores some metadata information across restarts ([#905](https://github.com/mozilla/application-services/issues/905))

# v0.24.0 (_2018-04-08_)

[Full Changelog](https://github.com/mozilla/application-services/compare/v0.23.0...v0.24.0)
Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ class PushManager(
LibPushFFI.INSTANCE.push_subscribe(
this.handle.get(), channelID, scope, error)
}

return SubscriptionInfo.fromString(json)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,24 @@ class PushTest {
assertEquals("Result", plaintext, sresult)
}

@Test
fun testAesgcmDecryption_bad() {
val manager = getPushManager()
// call this to set the (hardcoded) test key and auth
manager.subscribe(testChannelid, "foo")

// These values should come from the delivered message content.
val result = manager.decrypt(
channelID = testChannelid,
body = aesgcmBlock["body"].toString(),
encoding = aesgcmBlock["enc"].toString(),
salt = aesgcmBlock["salt"].toString(),
dh = aesgcmBlock["dh"].toString()
)
val sresult = result.toString(Charset.forName("UTF-8"))
assertEquals("Result", plaintext, sresult)
}

@Test
fun testAes128gcmDecryption() {
val manager = getPushManager()
Expand Down
78 changes: 58 additions & 20 deletions components/push/communications/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ extern crate serde_json;
use serde_json::Value;
use std::collections::HashMap;
use url::Url;
use viaduct::{header_names, status_codes, Request};
use viaduct::{header_names, status_codes, Headers, Request};

use config::PushConfiguration;
use push_errors as error;
Expand Down Expand Up @@ -89,7 +89,11 @@ pub struct ConnectHttp {
}

// Connect to the Autopush server
pub fn connect(options: PushConfiguration) -> error::Result<ConnectHttp> {
pub fn connect(
options: PushConfiguration,
uaid: Option<String>,
auth: Option<String>,
) -> error::Result<ConnectHttp> {
// find connection via options

if options.socket_protocol.is_some() && options.http_protocol.is_some() {
Expand All @@ -112,10 +116,10 @@ pub fn connect(options: PushConfiguration) -> error::Result<ConnectHttp> {
};
*/
let connection = ConnectHttp {
uaid: None,
uaid,
options: options.clone(),
// database,
auth: None,
auth,
};

Ok(connection)
Expand All @@ -132,13 +136,31 @@ impl Connection for ConnectHttp {
}
let options = self.options.clone();
let bridge_type = &options.bridge_type.unwrap();
let url = format!(
let mut url = format!(
"{}://{}/v1/{}/{}/registration",
&options.http_protocol.unwrap(),
&options.server_host,
&bridge_type,
&options.sender_id
);
// Add the Authorization header if we have a prior subscription.
let mut auth_headers = Headers::new();
jrconlin marked this conversation as resolved.
Show resolved Hide resolved
if let Some(uaid) = &self.uaid {
url.push('/');
url.push_str(&uaid);
url.push_str("/subscription");
if self.auth.is_none() {
return Err(error::ErrorKind::CommunicationError(
"Missing auth secret for subscription".to_owned(),
)
.into());
}
auth_headers
.insert("Authorization", self.auth.clone().unwrap())
.map_err(|e| {
error::ErrorKind::CommunicationError(format!("Header error: {:?}", e))
})?;
}
let mut body = HashMap::new();
body.insert("token", options.registration_id.unwrap());
body.insert("channelID", channelid.to_owned());
Expand All @@ -162,7 +184,7 @@ impl Connection for ConnectHttp {
});
}
let url = Url::parse(&url)?;
let requested = match Request::post(url).json(&body).send() {
let requested = match Request::post(url).headers(auth_headers).json(&body).send() {
Ok(v) => v,
Err(e) => {
return Err(
Expand Down Expand Up @@ -422,7 +444,7 @@ mod test {
.with_header("content-type", "application/json")
.with_body(body)
.create();
let mut conn = connect(config.clone()).unwrap();
let mut conn = connect(config.clone(), None, None).unwrap();
let channel_id = hex::encode(crypto::get_bytes(16).unwrap());
let response = conn.subscribe(&channel_id).unwrap();
ap_mock.assert();
Expand All @@ -445,9 +467,12 @@ mod test {
.with_header("content-type", "application/json")
.with_body("{}")
.create();
let mut conn = connect(config.clone()).unwrap();
conn.uaid = Some(DUMMY_UAID.to_owned());
conn.auth = Some(SECRET.to_owned());
let conn = connect(
config.clone(),
Some(DUMMY_UAID.to_owned()),
Some(SECRET.to_owned()),
)
.unwrap();
let response = conn.unsubscribe(Some(DUMMY_CHID)).unwrap();
ap_mock.assert();
assert!(response);
Expand All @@ -463,9 +488,12 @@ mod test {
.with_header("content-type", "application/json")
.with_body("{}")
.create();
let mut conn = connect(config.clone()).unwrap();
conn.uaid = Some(DUMMY_UAID.to_owned());
conn.auth = Some(SECRET.to_owned());
let conn = connect(
config.clone(),
Some(DUMMY_UAID.to_owned()),
Some(SECRET.to_owned()),
)
.unwrap();
//TODO: Add record to nuke.
let response = conn.unsubscribe(None).unwrap();
ap_mock.assert();
Expand All @@ -482,12 +510,19 @@ mod test {
.with_header("content-type", "application/json")
.with_body("{}")
.create();
let mut conn = connect(config.clone()).unwrap();
conn.uaid = Some(DUMMY_UAID.to_owned());
conn.auth = Some(SECRET.to_owned());
let mut conn = connect(
config.clone(),
Some(DUMMY_UAID.to_owned()),
Some(SECRET.to_owned()),
)
.unwrap();

let response = conn.update("NewTokenValue").unwrap();
ap_mock.assert();
assert!(conn.options.registration_id == Some("NewTokenValue".to_owned()));
assert_eq!(
conn.options.registration_id,
Some("NewTokenValue".to_owned())
);
assert!(response);
}
// CHANNEL LIST
Expand All @@ -506,9 +541,12 @@ mod test {
.with_header("content-type", "application/json")
.with_body(body_cl_success)
.create();
let mut conn = connect(config.clone()).unwrap();
conn.uaid = Some(DUMMY_UAID.to_owned());
conn.auth = Some(SECRET.to_owned());
let conn = connect(
config.clone(),
Some(DUMMY_UAID.to_owned()),
Some(SECRET.to_owned()),
)
.unwrap();
let response = conn.channel_list().unwrap();
ap_mock.assert();
assert!(response == [DUMMY_CHID.to_owned()]);
Expand Down
4 changes: 3 additions & 1 deletion components/push/crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,9 @@ impl Cryptography for Crypto {
let d_salt = salt.map(|v| { base64::decode_config(v, base64::URL_SAFE_NO_PAD).unwrap()});
let d_dh = dh.map(|v| { base64::decode_config(v, base64::URL_SAFE_NO_PAD).unwrap()});
*/
let d_body = base64::decode_config(body, base64::URL_SAFE_NO_PAD).unwrap();
let d_body = base64::decode_config(body, base64::URL_SAFE_NO_PAD).map_err(|e| {
error::ErrorKind::TranscodingError(format!("Could not parse incoming body: {:?}", e))
})?;

match encoding.to_lowercase().as_str() {
"aesgcm" => Self::decrypt_aesgcm(&key, &d_body, d_salt, d_dh),
Expand Down
37 changes: 36 additions & 1 deletion components/push/storage/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ pub trait Storage {
fn update_endpoint(&self, uaid: &str, channel_id: &str, endpoint: &str) -> Result<bool>;

fn update_native_id(&self, uaid: &str, native_id: &str) -> Result<bool>;

fn get_meta(&self, key: &str) -> Result<Option<String>>;

fn set_meta(&self, key: &str, value: &str) -> Result<()>;
}

pub struct PushDb {
Expand Down Expand Up @@ -171,6 +175,23 @@ impl Storage for PushDb {
)?;
Ok(affected_rows == 1)
}

fn get_meta(&self, key: &str) -> Result<Option<String>> {
// Get the most recent UAID (which should be the same value across all records,
// but paranoia)
self.try_query_one(
"SELECT value FROM meta_data where key = :key limit 1",
&[(":key", &key)],
true,
)
.map_err(|e| ErrorKind::StorageSqlError(e).into())
}

fn set_meta(&self, key: &str, value: &str) -> Result<()> {
let query = "INSERT or REPLACE into meta_data (key, value) values (:k, :v)";
self.execute_named_cached(query, &[(":k", &key), (":v", &value)])?;
Ok(())
}
}

#[cfg(test)]
Expand Down Expand Up @@ -239,7 +260,21 @@ mod test {
assert!(db.get_record(DUMMY_UAID, &rec.channel_id)?.is_some());
db.delete_all_records(DUMMY_UAID)?;
assert!(db.get_record(DUMMY_UAID, &rec.channel_id)?.is_none());
assert!(db.get_record(DUMMY_UAID, &rec2.channel_id)?.is_none());
assert!(db.get_record(DUMMY_UAID, &rec.channel_id)?.is_none());
Ok(())
}

#[test]
fn meta() -> Result<()> {
use super::Storage;
let db = PushDb::open_in_memory()?;
let no_rec = db.get_meta("uaid")?;
assert_eq!(no_rec, None);
db.set_meta("uaid", DUMMY_UAID)?;
db.set_meta("fruit", "apple")?;
db.set_meta("fruit", "banana")?;
assert_eq!(db.get_meta("uaid")?, Some(DUMMY_UAID.to_owned()));
assert_eq!(db.get_meta("fruit")?, Some("banana".to_owned()));
Ok(())
}
}
11 changes: 11 additions & 0 deletions components/push/storage/src/record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ use crate::types::Timestamp;

pub type ChannelID = String;

#[derive(Clone, Debug, PartialEq)]
pub struct MetaRecord {
/// Meta information are various push related values that need to persist across restarts.
/// e.g. "UAID", server "auth" token, etc. This table should not be exposed outside of
/// the push component.
// User Agent unique identifier
pub key: String,
// Server authorization token
pub val: String,
}

#[derive(Clone, Debug, PartialEq)]
pub struct PushRecord {
// User Agent's unique identifier
Expand Down
23 changes: 13 additions & 10 deletions components/push/storage/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use sql_support::ConnExt;

use push_errors::Result;

const VERSION: i64 = 1;
const VERSION: i64 = 2;

const CREATE_TABLE_PUSH_SQL: &str = include_str!("schema.sql");

Expand Down Expand Up @@ -37,20 +37,23 @@ pub fn init(db: &Connection) -> Result<()> {
Ok(())
}

fn upgrade(_db: &Connection, from: i64) -> Result<()> {
fn upgrade(db: &Connection, from: i64) -> Result<()> {
log::debug!("Upgrading schema from {} to {}", from, VERSION);
if from == VERSION {
return Ok(());
match from {
VERSION => Ok(()),
0 => create(db),
1 => create(db),
_ => panic!("sorry, no upgrades yet - delete your db!"),
}
panic!("sorry, no upgrades yet - delete your db!");
}

pub fn create(db: &Connection) -> Result<()> {
log::debug!("Creating schema");
db.execute_all(&[
CREATE_TABLE_PUSH_SQL,
&format!("PRAGMA user_version = {version}", version = VERSION),
])?;
let statements = format!(
"{create}\n\nPRAGMA user_version = {version}",
create = CREATE_TABLE_PUSH_SQL,
version = VERSION
);
db.execute_batch(&statements)?;

Ok(())
}
13 changes: 10 additions & 3 deletions components/push/storage/src/schema.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
-- XXX: maybe push_sub?
CREATE TABLE IF NOT EXISTS push_record (
CREATE TABLE
IF NOT EXISTS push_record
(
uaid TEXT NOT NULL,
channel_id TEXT NOT NULL UNIQUE,
endpoint TEXT NOT NULL UNIQUE,
Expand All @@ -10,4 +11,10 @@ CREATE TABLE IF NOT EXISTS push_record (
native_id TEXT,
PRIMARY KEY (uaid, channel_id)
);
CREATE UNIQUE INDEX channel_id_idx ON push_record(channel_id);

CREATE TABLE
IF NOT EXISTS meta_data
(
key TEXT PRIMARY KEY,
value NOT NULL
) without ROWID;
1 change: 1 addition & 0 deletions components/push/subscriber/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ license = "MPL-2.0"

[dependencies]
base64 = "0.10"
log = "0.4"
serde = "1.0"
serde_json = "1.0"

Expand Down