mirror of https://github.com/LemmyNet/lemmy.git
Merge pull request 'Add integration test to ensure that signatures are verified' (#103) from integration-test into main
Reviewed-on: https://yerbamate.dev/LemmyNet/lemmy/pulls/103pull/1146/head
commit
8e69801e24
|
@ -20,7 +20,7 @@ before_install:
|
||||||
# Change dir
|
# Change dir
|
||||||
- cd docker/travis
|
- cd docker/travis
|
||||||
script:
|
script:
|
||||||
- "./run-tests.sh"
|
- "./run-tests.bash"
|
||||||
deploy:
|
deploy:
|
||||||
provider: script
|
provider: script
|
||||||
script: bash docker_push.sh
|
script: bash docker_push.sh
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
# Docker Development
|
# Docker Development
|
||||||
|
|
||||||
|
## Dependencies (on Ubuntu)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install git docker-compose
|
||||||
|
sudo systemctl start docker
|
||||||
|
git clone https://github.com/LemmyNet/lemmy
|
||||||
|
```
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt install git docker-compose
|
|
||||||
git clone https://github.com/LemmyNet/lemmy
|
|
||||||
cd lemmy/docker/dev
|
cd lemmy/docker/dev
|
||||||
sudo docker-compose up --no-deps --build
|
sudo docker-compose up --no-deps --build
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,21 +1,9 @@
|
||||||
# Federation Development
|
# Federation Development
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
If you don't have a local clone of the Lemmy repo yet, just run the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/LemmyNet/lemmy
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running locally
|
## Running locally
|
||||||
|
|
||||||
You need to have the following packages installed, the Docker service needs to be running.
|
Install the dependencies as described in [Docker development](contributing_docker_development.md). Then run the following
|
||||||
|
|
||||||
- docker
|
|
||||||
- docker-compose
|
|
||||||
|
|
||||||
Then run the following
|
|
||||||
```bash
|
```bash
|
||||||
cd docker/federation
|
cd docker/federation
|
||||||
./start-local-instances.bash
|
./start-local-instances.bash
|
||||||
|
@ -40,15 +28,6 @@ To start federation between instances, visit one of them and search for a user,
|
||||||
|
|
||||||
Firefox containers are a good way to test them interacting.
|
Firefox containers are a good way to test them interacting.
|
||||||
|
|
||||||
## Integration tests
|
|
||||||
|
|
||||||
To run a suite of suite of federation integration tests:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd docker/federation
|
|
||||||
./run-tests.bash
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running on a server
|
## Running on a server
|
||||||
|
|
||||||
Note that federation is currently in alpha. **Only use it for testing**, not on any production server, and be aware that turning on federation may break your instance.
|
Note that federation is currently in alpha. **Only use it for testing**, not on any production server, and be aware that turning on federation may break your instance.
|
||||||
|
|
|
@ -12,5 +12,9 @@ psql -U lemmy -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
|
||||||
|
|
||||||
### Federation
|
### Federation
|
||||||
|
|
||||||
Install the [Docker development dependencies](contributing_docker_development.md), and execute
|
Install the [Docker development dependencies](contributing_docker_development.md), and execute:
|
||||||
`docker/federation-test/run-tests.sh`
|
|
||||||
|
```
|
||||||
|
cd docker/federation
|
||||||
|
./run-tests.bash
|
||||||
|
```
|
||||||
|
|
|
@ -57,7 +57,7 @@ impl APIError {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LemmyError {
|
pub struct LemmyError {
|
||||||
inner: anyhow::Error,
|
pub inner: anyhow::Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<T> for LemmyError
|
impl<T> From<T> for LemmyError
|
||||||
|
|
|
@ -79,7 +79,6 @@ impl ActixJob for SendActivityTask {
|
||||||
.post(to_url.as_str())
|
.post(to_url.as_str())
|
||||||
.header("Content-Type", "application/json");
|
.header("Content-Type", "application/json");
|
||||||
|
|
||||||
// TODO: i believe we have to do the signing in here because it is only valid for a few seconds
|
|
||||||
let signed = sign(
|
let signed = sign(
|
||||||
request,
|
request,
|
||||||
self.activity.clone(),
|
self.activity.clone(),
|
||||||
|
|
14
src/lib.rs
14
src/lib.rs
|
@ -50,7 +50,7 @@ pub struct LemmyContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LemmyContext {
|
impl LemmyContext {
|
||||||
pub fn create(
|
pub fn new(
|
||||||
pool: DbPool,
|
pool: DbPool,
|
||||||
chat_server: Addr<ChatServer>,
|
chat_server: Addr<ChatServer>,
|
||||||
client: Client,
|
client: Client,
|
||||||
|
@ -79,12 +79,12 @@ impl LemmyContext {
|
||||||
|
|
||||||
impl Clone for LemmyContext {
|
impl Clone for LemmyContext {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
LemmyContext {
|
LemmyContext::new(
|
||||||
pool: self.pool.clone(),
|
self.pool.clone(),
|
||||||
chat_server: self.chat_server.clone(),
|
self.chat_server.clone(),
|
||||||
client: self.client.clone(),
|
self.client.clone(),
|
||||||
activity_queue: self.activity_queue.clone(),
|
self.activity_queue.clone(),
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ async fn main() -> Result<(), LemmyError> {
|
||||||
|
|
||||||
// Create Http server with websocket support
|
// Create Http server with websocket support
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
let context = LemmyContext::create(
|
let context = LemmyContext::new(
|
||||||
pool.clone(),
|
pool.clone(),
|
||||||
chat_server.to_owned(),
|
chat_server.to_owned(),
|
||||||
Client::default(),
|
Client::default(),
|
||||||
|
|
|
@ -361,12 +361,7 @@ impl ChatServer {
|
||||||
|
|
||||||
let user_operation: UserOperation = UserOperation::from_str(&op)?;
|
let user_operation: UserOperation = UserOperation::from_str(&op)?;
|
||||||
|
|
||||||
let context = LemmyContext {
|
let context = LemmyContext::new(pool, addr, client, activity_queue);
|
||||||
pool,
|
|
||||||
chat_server: addr,
|
|
||||||
client,
|
|
||||||
activity_queue,
|
|
||||||
};
|
|
||||||
let args = Args {
|
let args = Args {
|
||||||
context,
|
context,
|
||||||
rate_limiter,
|
rate_limiter,
|
||||||
|
|
|
@ -0,0 +1,208 @@
|
||||||
|
extern crate lemmy_server;
|
||||||
|
|
||||||
|
use activitystreams::{
|
||||||
|
activity::{
|
||||||
|
kind::{CreateType, FollowType},
|
||||||
|
ActorAndObject,
|
||||||
|
},
|
||||||
|
base::{BaseExt, ExtendsExt},
|
||||||
|
object::{Note, ObjectExt},
|
||||||
|
};
|
||||||
|
use actix::prelude::*;
|
||||||
|
use actix_web::{test::TestRequest, web, web::Path, HttpRequest};
|
||||||
|
use chrono::Utc;
|
||||||
|
use diesel::{
|
||||||
|
r2d2::{ConnectionManager, Pool},
|
||||||
|
PgConnection,
|
||||||
|
};
|
||||||
|
use http_signature_normalization_actix::PrepareVerifyError;
|
||||||
|
use lemmy_db::{
|
||||||
|
community::{Community, CommunityForm},
|
||||||
|
user::{User_, *},
|
||||||
|
Crud,
|
||||||
|
ListingType,
|
||||||
|
SortType,
|
||||||
|
};
|
||||||
|
use lemmy_rate_limit::{rate_limiter::RateLimiter, RateLimit};
|
||||||
|
use lemmy_server::{
|
||||||
|
apub::{
|
||||||
|
activity_queue::create_activity_queue,
|
||||||
|
inbox::{
|
||||||
|
community_inbox,
|
||||||
|
community_inbox::community_inbox,
|
||||||
|
shared_inbox,
|
||||||
|
shared_inbox::shared_inbox,
|
||||||
|
user_inbox,
|
||||||
|
user_inbox::user_inbox,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
websocket::chat_server::ChatServer,
|
||||||
|
LemmyContext,
|
||||||
|
};
|
||||||
|
use lemmy_utils::{apub::generate_actor_keypair, settings::Settings};
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
fn create_context() -> LemmyContext {
|
||||||
|
let settings = Settings::get();
|
||||||
|
let db_url = settings.get_database_url();
|
||||||
|
let manager = ConnectionManager::<PgConnection>::new(&db_url);
|
||||||
|
let pool = Pool::builder()
|
||||||
|
.max_size(settings.database.pool_size)
|
||||||
|
.build(manager)
|
||||||
|
.unwrap();
|
||||||
|
let rate_limiter = RateLimit {
|
||||||
|
rate_limiter: Arc::new(Mutex::new(RateLimiter::default())),
|
||||||
|
};
|
||||||
|
let activity_queue = create_activity_queue();
|
||||||
|
let chat_server = ChatServer::startup(
|
||||||
|
pool.clone(),
|
||||||
|
rate_limiter.clone(),
|
||||||
|
Client::default(),
|
||||||
|
activity_queue.clone(),
|
||||||
|
)
|
||||||
|
.start();
|
||||||
|
LemmyContext::new(
|
||||||
|
pool,
|
||||||
|
chat_server,
|
||||||
|
Client::default(),
|
||||||
|
create_activity_queue(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_user(conn: &PgConnection, name: &str) -> User_ {
|
||||||
|
let user_keypair = generate_actor_keypair().unwrap();
|
||||||
|
let new_user = UserForm {
|
||||||
|
name: name.into(),
|
||||||
|
preferred_username: None,
|
||||||
|
password_encrypted: "nope".into(),
|
||||||
|
email: None,
|
||||||
|
matrix_user_id: None,
|
||||||
|
avatar: None,
|
||||||
|
banner: None,
|
||||||
|
admin: false,
|
||||||
|
banned: false,
|
||||||
|
updated: None,
|
||||||
|
show_nsfw: false,
|
||||||
|
theme: "darkly".into(),
|
||||||
|
default_sort_type: SortType::Hot as i16,
|
||||||
|
default_listing_type: ListingType::Subscribed as i16,
|
||||||
|
lang: "browser".into(),
|
||||||
|
show_avatars: true,
|
||||||
|
send_notifications_to_email: false,
|
||||||
|
actor_id: Some(format!("http://localhost:8536/u/{}", name).to_string()),
|
||||||
|
bio: None,
|
||||||
|
local: true,
|
||||||
|
private_key: Some(user_keypair.private_key),
|
||||||
|
public_key: Some(user_keypair.public_key),
|
||||||
|
last_refreshed_at: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
User_::create(&conn, &new_user).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_community(conn: &PgConnection, creator_id: i32) -> Community {
|
||||||
|
let new_community = CommunityForm {
|
||||||
|
name: "test_community".into(),
|
||||||
|
creator_id,
|
||||||
|
title: "test_community".to_owned(),
|
||||||
|
description: None,
|
||||||
|
category_id: 1,
|
||||||
|
nsfw: false,
|
||||||
|
removed: None,
|
||||||
|
deleted: None,
|
||||||
|
updated: None,
|
||||||
|
actor_id: None,
|
||||||
|
local: true,
|
||||||
|
private_key: None,
|
||||||
|
public_key: None,
|
||||||
|
last_refreshed_at: None,
|
||||||
|
published: None,
|
||||||
|
icon: None,
|
||||||
|
banner: None,
|
||||||
|
};
|
||||||
|
Community::create(&conn, &new_community).unwrap()
|
||||||
|
}
|
||||||
|
fn create_activity<'a, Activity, Return>(user_id: String) -> web::Json<Return>
|
||||||
|
where
|
||||||
|
for<'de> Return: Deserialize<'de> + 'a,
|
||||||
|
Activity: std::default::Default + Serialize,
|
||||||
|
{
|
||||||
|
let mut activity = ActorAndObject::<Activity>::new(user_id, Note::new().into_any_base().unwrap());
|
||||||
|
activity
|
||||||
|
.set_id(Url::parse("http://localhost:8536/create/1").unwrap())
|
||||||
|
.set_many_ccs(vec![Url::parse("http://localhost:8536/c/main").unwrap()]);
|
||||||
|
let activity = serde_json::to_value(&activity).unwrap();
|
||||||
|
let activity: Return = serde_json::from_value(activity).unwrap();
|
||||||
|
web::Json(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_http_request() -> HttpRequest {
|
||||||
|
let time1 = Utc::now().timestamp();
|
||||||
|
let time2 = Utc::now().timestamp();
|
||||||
|
let signature = format!(
|
||||||
|
r#"keyId="my-key-id",algorithm="hs2019",created="{}",expires="{}",headers="(request-target) (created) (expires) date content-type",signature="blah blah blah""#,
|
||||||
|
time1, time2
|
||||||
|
);
|
||||||
|
TestRequest::post()
|
||||||
|
.uri("http://localhost:8536/")
|
||||||
|
.header("Signature", signature)
|
||||||
|
.to_http_request()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_shared_inbox_expired_signature() {
|
||||||
|
let request = create_http_request();
|
||||||
|
let context = create_context();
|
||||||
|
let connection = &context.pool().get().unwrap();
|
||||||
|
let user = create_user(connection, "shared_inbox_rvgfd");
|
||||||
|
let activity =
|
||||||
|
create_activity::<CreateType, ActorAndObject<shared_inbox::ValidTypes>>(user.actor_id);
|
||||||
|
let response = shared_inbox(request, activity, web::Data::new(context)).await;
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", response.err().unwrap()),
|
||||||
|
format!("{}", PrepareVerifyError::Expired)
|
||||||
|
);
|
||||||
|
User_::delete(connection, user.id).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_user_inbox_expired_signature() {
|
||||||
|
let request = create_http_request();
|
||||||
|
let context = create_context();
|
||||||
|
let connection = &context.pool().get().unwrap();
|
||||||
|
let user = create_user(connection, "user_inbox_cgsax");
|
||||||
|
let activity =
|
||||||
|
create_activity::<CreateType, ActorAndObject<user_inbox::ValidTypes>>(user.actor_id);
|
||||||
|
let path = Path::<String> {
|
||||||
|
0: "username".to_string(),
|
||||||
|
};
|
||||||
|
let response = user_inbox(request, activity, path, web::Data::new(context)).await;
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", response.err().unwrap()),
|
||||||
|
format!("{}", PrepareVerifyError::Expired)
|
||||||
|
);
|
||||||
|
User_::delete(connection, user.id).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_community_inbox_expired_signature() {
|
||||||
|
let context = create_context();
|
||||||
|
let connection = &context.pool().get().unwrap();
|
||||||
|
let user = create_user(connection, "community_inbox_hrxa");
|
||||||
|
let community = create_community(connection, user.id);
|
||||||
|
let request = create_http_request();
|
||||||
|
let activity =
|
||||||
|
create_activity::<FollowType, ActorAndObject<community_inbox::ValidTypes>>(user.actor_id);
|
||||||
|
let path = Path::<String> { 0: community.name };
|
||||||
|
let response = community_inbox(request, activity, path, web::Data::new(context)).await;
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", response.err().unwrap()),
|
||||||
|
format!("{}", PrepareVerifyError::Expired)
|
||||||
|
);
|
||||||
|
User_::delete(connection, user.id).unwrap();
|
||||||
|
Community::delete(connection, community.id).unwrap();
|
||||||
|
}
|
Loading…
Reference in New Issue