diff --git a/CHANGELOG.md b/CHANGELOG.md index b2d471d..cc48a98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,12 +15,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Deprecated -- Deprecated `proxy_url` configuration paramter (replaced by `federation.proxy_url`). +- Deprecated `proxy_url` configuration parameter (replaced by `federation.proxy_url`). ### Fixed - Prevent `delete-extraneous-posts` command from removing locally-linked posts. - Make webfinger response compatible with GNU Social account lookup. +- Prefer `Group` actor when doing webfinger query on Lemmy server. ## [1.14.0] - 2023-02-22 diff --git a/src/activitypub/fetcher/fetchers.rs b/src/activitypub/fetcher/fetchers.rs index 71c7bfe..9f15465 100644 --- a/src/activitypub/fetcher/fetchers.rs +++ b/src/activitypub/fetcher/fetchers.rs @@ -12,9 +12,10 @@ use mitra_utils::{ use crate::activitypub::{ actors::types::Actor, - constants::AP_MEDIA_TYPE, + constants::{AP_CONTEXT, AP_MEDIA_TYPE}, identifiers::{local_actor_key_id, local_instance_actor_id}, types::Object, + vocabulary::GROUP, }; use crate::http_signatures::create::{ create_http_signature, @@ -183,11 +184,28 @@ pub async fn perform_webfinger_query( .error_for_status()? .text().await?; let jrd: JsonResourceDescriptor = serde_json::from_str(&webfinger_data)?; - let link = jrd.links.into_iter() - .find(|link| link.rel == "self") - .ok_or(FetchError::OtherError("self link not found"))?; - let actor_url = link.href - .ok_or(FetchError::OtherError("account href not found"))?; + // Lemmy servers can have Group and Person actors with the same name + // https://github.com/LemmyNet/lemmy/issues/2037 + let ap_type_property = format!("{}#type", AP_CONTEXT); + let group_link = jrd.links.iter() + .find(|link| { + link.rel == "self" && + link.properties + .get(&ap_type_property) + .map(|val| val.as_str()) == Some(GROUP) + }); + let link = if let Some(link) = group_link { + // Prefer Group if the actor type is provided + link + } else { + // Otherwise take first "self" link + jrd.links.iter() + .find(|link| link.rel == "self") + .ok_or(FetchError::OtherError("self link not found"))? + }; + let actor_url = link.href.as_ref() + .ok_or(FetchError::OtherError("account href not found"))? + .to_string(); Ok(actor_url) } diff --git a/src/activitypub/vocabulary.rs b/src/activitypub/vocabulary.rs index 56eb171..83d57d6 100644 --- a/src/activitypub/vocabulary.rs +++ b/src/activitypub/vocabulary.rs @@ -18,6 +18,7 @@ pub const UNDO: &str = "Undo"; pub const UPDATE: &str = "Update"; // Actor types +pub const GROUP: &str = "Group"; pub const PERSON: &str = "Person"; pub const SERVICE: &str = "Service"; diff --git a/src/nodeinfo/views.rs b/src/nodeinfo/views.rs index aedbb24..f901b9f 100644 --- a/src/nodeinfo/views.rs +++ b/src/nodeinfo/views.rs @@ -22,6 +22,7 @@ pub async fn get_nodeinfo( rel: "http://nodeinfo.diaspora.software/ns/schema/2.0".to_string(), media_type: None, href: Some(nodeinfo_2_0_url), + properties: Default::default(), }; let jrd = JsonResourceDescriptor { subject: config.instance_url(), diff --git a/src/webfinger/types.rs b/src/webfinger/types.rs index d818711..65d2721 100644 --- a/src/webfinger/types.rs +++ b/src/webfinger/types.rs @@ -1,6 +1,9 @@ /// https://webfinger.net/ -use std::fmt; -use std::str::FromStr; +use std::{ + collections::HashMap, + fmt, + str::FromStr, +}; use regex::Regex; use serde::{Serialize, Deserialize}; @@ -63,6 +66,9 @@ pub struct Link { pub media_type: Option, pub href: Option, + + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub properties: HashMap, } // https://datatracker.ietf.org/doc/html/rfc7033#section-4.4 diff --git a/src/webfinger/views.rs b/src/webfinger/views.rs index b48182e..833b597 100644 --- a/src/webfinger/views.rs +++ b/src/webfinger/views.rs @@ -62,11 +62,13 @@ async fn get_jrd( rel: "http://webfinger.net/rel/profile-page".to_string(), media_type: Some("text/html".to_string()), href: Some(actor_id.clone()), + properties: Default::default(), }; let link_actor = Link { rel: "self".to_string(), media_type: Some(AP_MEDIA_TYPE.to_string()), href: Some(actor_id), + properties: Default::default(), }; let jrd = JsonResourceDescriptor { subject: format!("acct:{}", actor_address),