mirror of
https://git.joinplu.me/Plume/Plume.git
synced 2024-11-25 04:51:27 +00:00
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:
parent
718e23ac85
commit
ab2998e214
9 changed files with 96 additions and 17 deletions
26
.codecov.yml
Normal file
26
.codecov.yml
Normal 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
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
45
src/inbox.rs
45
src/inbox.rs
|
@ -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)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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")));
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
Loading…
Reference in a new issue