Make Plume compile on release (#365)

* Remove use of String for body parameters

Create SignedJson and implement FromData for it

* Make Travis test on release

* Remove warning when installing and fix coverage
This commit is contained in:
fdb-hiroshima 2018-12-22 18:27:21 +01:00 committed by GitHub
parent 718e23ac85
commit ab2998e214
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 96 additions and 17 deletions

26
.codecov.yml Normal file
View file

@ -0,0 +1,26 @@
codecov:
notify:
require_ci_to_pass: yes
coverage:
precision: 2
round: down
range: "70...100"
status:
project: no
patch: no
changes: no
parsers:
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no
comment:
layout: "header, diff"
behavior: default
require_changes: no

View file

@ -29,12 +29,12 @@ jobs:
name: "Build with postgresql" name: "Build with postgresql"
env: env:
- MIGRATION_DIR=migrations/postgres FEATURES=postgres DATABASE_URL=postgres://postgres@localhost/plume - MIGRATION_DIR=migrations/postgres FEATURES=postgres DATABASE_URL=postgres://postgres@localhost/plume
script: cargo build --no-default-features --features="${FEATURES}" script: cargo build --no-default-features --features="${FEATURES}" --release
- stage: build - stage: build
name: "Build with sqlite" name: "Build with sqlite"
env: env:
- MIGRATION_DIR=migrations/sqlite FEATURES=sqlite DATABASE_URL=plume.sqlite3 - MIGRATION_DIR=migrations/sqlite FEATURES=sqlite DATABASE_URL=plume.sqlite3
script: cargo build --no-default-features --features="${FEATURES}" script: cargo build --no-default-features --features="${FEATURES}" --release
- stage: test and coverage - stage: test and coverage
name: "Test with potgresql backend" name: "Test with potgresql backend"
env: env:

View file

@ -14,8 +14,8 @@ WORKDIR /app
COPY Cargo.toml Cargo.lock ./ COPY Cargo.toml Cargo.lock ./
RUN cargo install diesel_cli --no-default-features --features postgres --version '=1.3.0' RUN cargo install diesel_cli --no-default-features --features postgres --version '=1.3.0'
COPY . . COPY . .
RUN cargo install --force --no-default-features --features postgres RUN cargo install --path ./ --force --no-default-features --features postgres
RUN cargo install --path plume-cli --force --no-default-features --features postgres RUN cargo install --path plume-cli --force --no-default-features --features postgres
RUN rm -rf target/debug/incremental RUN rm -rf target/release/incremental
CMD ["plume"] CMD ["plume"]
EXPOSE 7878 EXPOSE 7878

View file

@ -182,7 +182,7 @@ When in doubt, run them.
Then, you'll need to install Plume and the CLI tools to manage your instance. Then, you'll need to install Plume and the CLI tools to manage your instance.
``` ```
cargo install --no-default-features --features $FEATURES cargo install --no-default-features --features $FEATURES --path ./
cargo install --no-default-features --features $FEATURES --path plume-cli cargo install --no-default-features --features $FEATURES --path plume-cli
``` ```

View file

@ -41,6 +41,10 @@ impl Digest {
} }
} }
pub fn verify_header(&self, other: &Digest) -> bool {
self.value()==other.value()
}
pub fn algorithm(&self) -> &str { pub fn algorithm(&self) -> &str {
let pos = self let pos = self
.0 .0
@ -69,6 +73,13 @@ impl Digest {
Err(()) Err(())
} }
} }
pub fn from_body(body: &str) -> Self {
let mut hasher = Hasher::new(MessageDigest::sha256()).expect("Digest::digest: initialization error");
hasher.update(body.as_bytes()).expect("Digest::digest: content insertion error");
let res = base64::encode(&hasher.finish().expect("Digest::digest: finalizing error"));
Digest(format!("SHA-256={}", res))
}
} }
pub fn headers() -> HeaderMap { pub fn headers() -> HeaderMap {

View file

@ -129,7 +129,7 @@ impl SignatureValidity {
pub fn verify_http_headers<S: Signer + ::std::fmt::Debug>( pub fn verify_http_headers<S: Signer + ::std::fmt::Debug>(
sender: &S, sender: &S,
all_headers: &HeaderMap, all_headers: &HeaderMap,
data: &str, data: &request::Digest,
) -> SignatureValidity { ) -> SignatureValidity {
let sig_header = all_headers.get_one("Signature"); let sig_header = all_headers.get_one("Signature");
if sig_header.is_none() { if sig_header.is_none() {
@ -176,7 +176,7 @@ pub fn verify_http_headers<S: Signer + ::std::fmt::Debug>(
} }
let digest = all_headers.get_one("digest").unwrap_or(""); let digest = all_headers.get_one("digest").unwrap_or("");
let digest = request::Digest::from_header(digest); let digest = request::Digest::from_header(digest);
if !digest.map(|d| d.verify(&data)).unwrap_or(false) { if !digest.map(|d| d.verify_header(&data)).unwrap_or(false) {
// signature was valid, but body content does not match its digest // signature was valid, but body content does not match its digest
return SignatureValidity::Invalid; return SignatureValidity::Invalid;
} }

View file

@ -11,11 +11,21 @@ use activitypub::{
object::Tombstone object::Tombstone
}; };
use failure::Error; use failure::Error;
use rocket::{
data::*,
http::Status,
Outcome::{self, *},
Request,
};
use rocket_contrib::json::*;
use serde::Deserialize;
use serde_json; use serde_json;
use std::io::Read;
use plume_common::activity_pub::{ use plume_common::activity_pub::{
inbox::{Deletable, FromActivity, InboxError}, inbox::{Deletable, FromActivity, InboxError},
Id, Id,request::Digest,
}; };
use plume_models::{ use plume_models::{
comments::Comment, follows::Follow, instance::Instance, likes, posts::Post, reshares::Reshare, comments::Comment, follows::Follow, instance::Instance, likes, posts::Post, reshares::Reshare,
@ -125,3 +135,36 @@ pub trait Inbox {
impl Inbox for Instance {} impl Inbox for Instance {}
impl Inbox for User {} impl Inbox for User {}
const JSON_LIMIT: u64 = 1 << 20;
pub struct SignedJson<T>(pub Digest, pub Json<T>);
impl<'a, T: Deserialize<'a>> FromData<'a> for SignedJson<T> {
type Error = JsonError<'a>;
type Owned = String;
type Borrowed = str;
fn transform(r: &Request, d: Data) -> Transform<Outcome<Self::Owned, (Status, Self::Error), Data>> {
let size_limit = r.limits().get("json").unwrap_or(JSON_LIMIT);
let mut s = String::with_capacity(512);
match d.open().take(size_limit).read_to_string(&mut s) {
Ok(_) => Transform::Borrowed(Success(s)),
Err(e) => Transform::Borrowed(Failure((Status::BadRequest, JsonError::Io(e))))
}
}
fn from_data(_: &Request, o: Transformed<'a, Self>) -> Outcome<Self, (Status, Self::Error), Data> {
let string = o.borrowed()?;
match serde_json::from_str(&string) {
Ok(v) => Success(SignedJson(Digest::from_body(&string),Json(v))),
Err(e) => {
if e.is_data() {
Failure((Status::UnprocessableEntity, JsonError::Parse(string, e)))
} else {
Failure((Status::BadRequest, JsonError::Parse(string, e)))
}
}
}
}
}

View file

@ -16,7 +16,7 @@ use plume_models::{
safe_string::SafeString, safe_string::SafeString,
instance::* instance::*
}; };
use inbox::Inbox; use inbox::{Inbox, SignedJson};
use routes::Page; use routes::Page;
use template_utils::Ructe; use template_utils::Ructe;
use Searcher; use Searcher;
@ -186,15 +186,15 @@ pub fn ban(_admin: Admin, conn: DbConn, id: i32, searcher: Searcher) -> Redirect
} }
#[post("/inbox", data = "<data>")] #[post("/inbox", data = "<data>")]
pub fn shared_inbox(conn: DbConn, data: String, headers: Headers, searcher: Searcher) -> Result<String, status::BadRequest<&'static str>> { pub fn shared_inbox(conn: DbConn, data: SignedJson<serde_json::Value>, headers: Headers, searcher: Searcher) -> Result<String, status::BadRequest<&'static str>> {
let act: serde_json::Value = serde_json::from_str(&data[..]).expect("instance::shared_inbox: deserialization error"); let act = data.1.into_inner();
let activity = act.clone(); let activity = act.clone();
let actor_id = activity["actor"].as_str() let actor_id = activity["actor"].as_str()
.or_else(|| activity["actor"]["id"].as_str()).ok_or(status::BadRequest(Some("Missing actor id for activity")))?; .or_else(|| activity["actor"]["id"].as_str()).ok_or(status::BadRequest(Some("Missing actor id for activity")))?;
let actor = User::from_url(&conn, actor_id).expect("instance::shared_inbox: user error"); let actor = User::from_url(&conn, actor_id).expect("instance::shared_inbox: user error");
if !verify_http_headers(&actor, &headers.0, &data).is_secure() && if !verify_http_headers(&actor, &headers.0, &data.0).is_secure() &&
!act.clone().verify(&actor) { !act.clone().verify(&actor) {
println!("Rejected invalid activity supposedly from {}, with headers {:?}", actor.username, headers.0); println!("Rejected invalid activity supposedly from {}, with headers {:?}", actor.username, headers.0);
return Err(status::BadRequest(Some("Invalid signature"))); return Err(status::BadRequest(Some("Invalid signature")));

View file

@ -9,7 +9,7 @@ use rocket_i18n::I18n;
use serde_json; use serde_json;
use validator::{Validate, ValidationError, ValidationErrors}; use validator::{Validate, ValidationError, ValidationErrors};
use inbox::Inbox; use inbox::{Inbox, SignedJson};
use plume_common::activity_pub::{ use plume_common::activity_pub::{
broadcast, broadcast,
inbox::{Deletable, FromActivity, Notify}, inbox::{Deletable, FromActivity, Notify},
@ -349,13 +349,12 @@ pub fn outbox(name: String, conn: DbConn) -> Option<ActivityStream<OrderedCollec
pub fn inbox( pub fn inbox(
name: String, name: String,
conn: DbConn, conn: DbConn,
data: String, data: SignedJson<serde_json::Value>,
headers: Headers, headers: Headers,
searcher: Searcher, searcher: Searcher,
) -> Result<String, Option<status::BadRequest<&'static str>>> { ) -> Result<String, Option<status::BadRequest<&'static str>>> {
let user = User::find_local(&*conn, &name).ok_or(None)?; let user = User::find_local(&*conn, &name).ok_or(None)?;
let act: serde_json::Value = let act = data.1.into_inner();
serde_json::from_str(&data).expect("user::inbox: deserialization error");
let activity = act.clone(); let activity = act.clone();
let actor_id = activity["actor"] let actor_id = activity["actor"]
@ -366,7 +365,7 @@ pub fn inbox(
))))?; ))))?;
let actor = User::from_url(&conn, actor_id).expect("user::inbox: user error"); let actor = User::from_url(&conn, actor_id).expect("user::inbox: user error");
if !verify_http_headers(&actor, &headers.0, &data).is_secure() if !verify_http_headers(&actor, &headers.0, &data.0).is_secure()
&& !act.clone().verify(&actor) && !act.clone().verify(&actor)
{ {
println!( println!(