Skip to content

Commit

Permalink
bug: store meta-data for push component in the database.
Browse files Browse the repository at this point in the history
Push now stores uaid and the server secret auth in a table in the
push.db. the meta_data table allows any key/value data to be stored to
allow for additional future values (if needed).

Closes #905
  • Loading branch information
jrconlin committed Apr 8, 2019
1 parent 3a3adc8 commit 8a19746
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 40 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,9 @@
# Unreleased Changes

### What's fixed

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

**See [the release process docs](docs/howtos/cut-a-new-release.md) for the steps to take when cutting a new release.**

[Full Changelog](https://github.com/mozilla/application-services/compare/v0.24.0...master)
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.

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

return SubscriptionInfo.fromString(json)
}

Expand Down
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
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();
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
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
38 changes: 37 additions & 1 deletion components/push/storage/src/db.rs
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,24 @@ 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)
let query = "SELECT value FROM meta_data where key = :key limit 1";
let mut statement = self.prepare(query)?;
let mut rows = statement.query_named(&[(":key", &key)])?;
if let Some(row) = rows.next() {
return Ok(Some(row?.get_checked("value")?));
}
Ok(None)
}

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(query, &[(":k", &key), (":v", &value)])?;
Ok(())
}
}

#[cfg(test)]
Expand Down Expand Up @@ -239,7 +261,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
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
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(())
}
17 changes: 14 additions & 3 deletions components/push/storage/src/schema.sql
@@ -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 @@ -8,6 +9,16 @@ CREATE TABLE IF NOT EXISTS push_record (
ctime INTEGER NOT NULL,
app_server_key TEXT,
native_id TEXT,
PRIMARY KEY (uaid, channel_id)
PRIMARY KEY
(uaid, channel_id)
);

DROP INDEX IF EXISTS channel_id_idx;
CREATE UNIQUE INDEX channel_id_idx ON push_record(channel_id);

CREATE TABLE
IF NOT EXISTS meta_data
(
key TEXT NOT NULL UNIQUE PRIMARY KEY,
value NOT NULL
) without ROWID;
1 change: 1 addition & 0 deletions components/push/subscriber/Cargo.toml
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

0 comments on commit 8a19746

Please sign in to comment.