Only allow authenticated users to fetch remote objects (#2493)

* Only allow authenticated users to fetch remote objects

* try to fix api tests
This commit is contained in:
Nutomic 2022-10-13 16:30:31 +00:00 committed by GitHub
parent cb559178bd
commit 6c3e984ad1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 53 additions and 48 deletions

View file

@ -30,6 +30,7 @@ import {
unfollows, unfollows,
getComments, getComments,
getCommentParentId, getCommentParentId,
resolveCommunity,
} from './shared'; } from './shared';
let postRes: PostResponse; let postRes: PostResponse;
@ -293,8 +294,8 @@ test('Comment Search', async () => {
test('A and G subscribe to B (center) A posts, G mentions B, it gets announced to A', async () => { test('A and G subscribe to B (center) A posts, G mentions B, it gets announced to A', async () => {
// Create a local post // Create a local post
let alphaCommunity = await createCommunity(alpha, "main"); let alphaCommunity = (await resolveCommunity(alpha, "!main@lemmy-alpha:8541")).community.unwrap();
let alphaPost = await createPost(alpha, alphaCommunity.community_view.community.id); let alphaPost = await createPost(alpha, alphaCommunity.community.id);
expect(alphaPost.post_view.community.local).toBe(true); expect(alphaPost.post_view.community.local).toBe(true);
// Make sure gamma sees it // Make sure gamma sees it

View file

@ -37,7 +37,7 @@ test('Follow federated community', async () => {
c => c.community.local == false c => c.community.local == false
).community.id; ).community.id;
expect(remoteCommunityId).toBeDefined(); expect(remoteCommunityId).toBeDefined();
expect(site.my_user.unwrap().follows.length).toBe(1); expect(site.my_user.unwrap().follows.length).toBe(2);
// Test an unfollow // Test an unfollow
let unfollow = await followCommunity(alpha, false, remoteCommunityId); let unfollow = await followCommunity(alpha, false, remoteCommunityId);
@ -45,5 +45,5 @@ test('Follow federated community', async () => {
// Make sure you are unsubbed locally // Make sure you are unsubbed locally
let siteUnfollowCheck = await getSite(alpha); let siteUnfollowCheck = await getSite(alpha);
expect(siteUnfollowCheck.my_user.unwrap().follows.length).toBe(0); expect(siteUnfollowCheck.my_user.unwrap().follows.length).toBe(1);
}); });

View file

@ -32,7 +32,8 @@ import {
registerUser, registerUser,
API, API,
getSite, getSite,
unfollows unfollows,
resolveCommunity
} from './shared'; } from './shared';
let betaCommunity: CommunityView; let betaCommunity: CommunityView;
@ -226,7 +227,8 @@ test('Delete a post', async () => {
}); });
test('Remove a post from admin and community on different instance', async () => { test('Remove a post from admin and community on different instance', async () => {
let postRes = await createPost(gamma, betaCommunity.community.id); let gammaCommunity = await resolveCommunity(gamma, betaCommunity.community.actor_id);
let postRes = await createPost(gamma, gammaCommunity.community.unwrap().community.id);
let alphaPost = (await resolvePost(alpha, postRes.post_view.post)).post.unwrap(); let alphaPost = (await resolvePost(alpha, postRes.post_view.post)).post.unwrap();
let removedPost = await removePost(alpha, true, alphaPost.post); let removedPost = await removePost(alpha, true, alphaPost.post);

View file

@ -174,9 +174,9 @@ export async function setupLogins() {
editSiteForm.auth = epsilon.auth.unwrap(); editSiteForm.auth = epsilon.auth.unwrap();
await epsilon.client.editSite(editSiteForm); await epsilon.client.editSite(editSiteForm);
// Create the main beta community, follow it // Create the main alpha/beta communities
await createCommunity(alpha, "main");
await createCommunity(beta, "main"); await createCommunity(beta, "main");
await followBeta(beta);
} }
export async function createPost( export async function createPost(

View file

@ -19,8 +19,13 @@ import {
API, API,
resolveComment, resolveComment,
saveUserSettingsFederated, saveUserSettingsFederated,
setupLogins,
} from './shared'; } from './shared';
beforeAll(async () => {
await setupLogins();
});
let apShortname: string; let apShortname: string;
function assertUserFederation(userOne: PersonViewSafe, userTwo: PersonViewSafe) { function assertUserFederation(userOne: PersonViewSafe, userTwo: PersonViewSafe) {

View file

@ -5,7 +5,7 @@ use lemmy_api_common::{
site::{ResolveObject, ResolveObjectResponse}, site::{ResolveObject, ResolveObjectResponse},
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt}, utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt},
}; };
use lemmy_apub::fetcher::search::{search_by_apub_id, SearchableObjects}; use lemmy_apub::fetcher::search::{search_query_to_object_id, SearchableObjects};
use lemmy_db_schema::{newtypes::PersonId, utils::DbPool}; use lemmy_db_schema::{newtypes::PersonId, utils::DbPool};
use lemmy_db_views::structs::{CommentView, PostView}; use lemmy_db_views::structs::{CommentView, PostView};
use lemmy_db_views_actor::structs::{CommunityView, PersonViewSafe}; use lemmy_db_views_actor::structs::{CommunityView, PersonViewSafe};
@ -27,7 +27,7 @@ impl Perform for ResolveObject {
.await?; .await?;
check_private_instance(&local_user_view, context.pool()).await?; check_private_instance(&local_user_view, context.pool()).await?;
let res = search_by_apub_id(&self.q, context) let res = search_query_to_object_id(&self.q, local_user_view.is_none(), context)
.await .await
.map_err(|e| e.with_message("couldnt_find_object"))?; .map_err(|e| e.with_message("couldnt_find_object"))?;
convert_response(res, local_user_view.map(|l| l.person.id), context.pool()) convert_response(res, local_user_view.map(|l| l.person.id), context.pool())

View file

@ -45,7 +45,7 @@ where
Ok(actor?) Ok(actor?)
} else { } else {
// Fetch the actor from its home instance using webfinger // Fetch the actor from its home instance using webfinger
let id = webfinger_resolve_actor::<Actor>(identifier, context, &mut 0).await?; let id = webfinger_resolve_actor::<Actor>(identifier, true, context, &mut 0).await?;
let actor: DbActor = blocking(context.pool(), move |conn| { let actor: DbActor = blocking(context.pool(), move |conn| {
DbActor::read_from_apub_id(conn, &id) DbActor::read_from_apub_id(conn, &id)
}) })

View file

@ -11,52 +11,44 @@ use lemmy_websocket::LemmyContext;
use serde::Deserialize; use serde::Deserialize;
use url::Url; use url::Url;
/// Attempt to parse the query as URL, and fetch an ActivityPub object from it. /// Converts search query to object id. The query can either be an URL, which will be treated as
/// /// ObjectId directly, or a webfinger identifier (@user@example.com or !community@example.com)
/// Some working examples for use with the `docker/federation/` setup: /// which gets resolved to an URL.
/// http://lemmy_alpha:8541/c/main, or !main@lemmy_alpha:8541
/// http://lemmy_beta:8551/u/lemmy_alpha, or @lemmy_beta@lemmy_beta:8551
/// http://lemmy_gamma:8561/post/3
/// http://lemmy_delta:8571/comment/2
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn search_by_apub_id( pub async fn search_query_to_object_id(
query: &str, query: &str,
local_only: bool,
context: &LemmyContext, context: &LemmyContext,
) -> Result<SearchableObjects, LemmyError> { ) -> Result<SearchableObjects, LemmyError> {
let request_counter = &mut 0; let request_counter = &mut 0;
let instance = local_instance(context); let object_id = match Url::parse(query) {
match Url::parse(query) { // its already an url, just go with it
Ok(url) => { Ok(url) => ObjectId::new(url),
ObjectId::new(url)
.dereference(context, instance, request_counter)
.await
}
Err(_) => { Err(_) => {
// not an url, try to resolve via webfinger
let mut chars = query.chars(); let mut chars = query.chars();
let kind = chars.next(); let kind = chars.next();
let identifier = chars.as_str(); let identifier = chars.as_str();
match kind { let id = match kind {
Some('@') => { Some('@') => {
let id = webfinger_resolve_actor::<ApubPerson>(identifier, local_only, context, request_counter)
webfinger_resolve_actor::<ApubPerson>(identifier, context, request_counter).await?; .await?
Ok(SearchableObjects::Person(
ObjectId::new(id)
.dereference(context, instance, request_counter)
.await?,
))
} }
Some('!') => { Some('!') => {
let id = webfinger_resolve_actor::<ApubCommunity>(identifier, local_only, context, request_counter)
webfinger_resolve_actor::<ApubCommunity>(identifier, context, request_counter).await?; .await?
Ok(SearchableObjects::Community(
ObjectId::new(id)
.dereference(context, instance, request_counter)
.await?,
))
} }
_ => Err(LemmyError::from_message("invalid query")), _ => return Err(LemmyError::from_message("invalid query")),
} };
ObjectId::new(id)
} }
};
if local_only {
object_id.dereference_local(context).await
} else {
object_id
.dereference(context, local_instance(context), request_counter)
.await
} }
} }

View file

@ -28,6 +28,7 @@ pub struct WebfingerResponse {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub(crate) async fn webfinger_resolve_actor<Kind>( pub(crate) async fn webfinger_resolve_actor<Kind>(
identifier: &str, identifier: &str,
local_only: bool,
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<DbUrl, LemmyError> ) -> Result<DbUrl, LemmyError>
@ -68,9 +69,14 @@ where
.filter_map(|l| l.href.clone()) .filter_map(|l| l.href.clone())
.collect(); .collect();
for l in links { for l in links {
let object = ObjectId::<Kind>::new(l) let object_id = ObjectId::<Kind>::new(l);
.dereference(context, local_instance(context), request_counter) let object = if local_only {
.await; object_id.dereference_local(context).await
} else {
object_id
.dereference(context, local_instance(context), request_counter)
.await
};
if object.is_ok() { if object.is_ok() {
return object.map(|o| o.actor_id().into()); return object.map(|o| o.actor_id().into());
} }

View file

@ -73,10 +73,9 @@ pub async fn collect_non_local_mentions(
.collect::<Vec<MentionData>>(); .collect::<Vec<MentionData>>();
for mention in &mentions { for mention in &mentions {
// TODO should it be fetching it every time?
let identifier = format!("{}@{}", mention.name, mention.domain); let identifier = format!("{}@{}", mention.name, mention.domain);
let actor_id = let actor_id =
webfinger_resolve_actor::<ApubPerson>(&identifier, context, request_counter).await; webfinger_resolve_actor::<ApubPerson>(&identifier, true, context, request_counter).await;
if let Ok(actor_id) = actor_id { if let Ok(actor_id) = actor_id {
let actor_id: ObjectId<ApubPerson> = ObjectId::new(actor_id); let actor_id: ObjectId<ApubPerson> = ObjectId::new(actor_id);
addressed_ccs.push(actor_id.to_string().parse()?); addressed_ccs.push(actor_id.to_string().parse()?);