fedimovies/src/activitypub/actors/types.rs

460 lines
15 KiB
Rust
Raw Normal View History

use std::collections::HashMap;
2023-04-24 15:35:32 +00:00
use serde::{de::Error as DeserializerError, Deserialize, Deserializer, Serialize};
2021-04-09 00:22:17 +00:00
use serde_json::{json, Value};
2023-04-25 13:49:35 +00:00
use fedimovies_config::Instance;
use fedimovies_models::{
2023-04-24 15:35:32 +00:00
profiles::types::{DbActor, DbActorPublicKey, ExtraField, IdentityProof, PaymentOption},
users::types::User,
};
2023-04-25 13:49:35 +00:00
use fedimovies_utils::{
2023-02-18 22:25:49 +00:00
crypto_rsa::{deserialize_private_key, get_public_key_pem},
urls::get_hostname,
};
2023-04-26 10:55:42 +00:00
use crate::activitypub::types::build_default_context;
2022-07-23 21:37:21 +00:00
use crate::activitypub::{
constants::{
2023-04-24 15:35:32 +00:00
AP_CONTEXT, MASTODON_CONTEXT, MITRA_CONTEXT, SCHEMA_ORG_CONTEXT, W3ID_SECURITY_CONTEXT,
},
identifiers::{
2023-04-24 15:35:32 +00:00
local_actor_id, local_actor_key_id, local_instance_actor_id, LocalActorCollection,
},
2023-03-03 22:08:38 +00:00
types::deserialize_value_array,
vocabulary::{IDENTITY_PROOF, IMAGE, LINK, PERSON, PROPERTY_VALUE, SERVICE},
2022-07-23 21:37:21 +00:00
};
use crate::errors::ValidationError;
use crate::media::get_file_url;
use crate::webfinger::types::ActorAddress;
use super::attachments::{
2023-04-24 15:35:32 +00:00
attach_extra_field, attach_identity_proof, attach_payment_option, parse_extra_field,
parse_identity_proof, parse_payment_option,
};
2021-04-09 00:22:17 +00:00
#[derive(Deserialize, Serialize)]
#[cfg_attr(test, derive(Default))]
2021-04-09 00:22:17 +00:00
#[serde(rename_all = "camelCase")]
pub struct PublicKey {
id: String,
owner: String,
pub public_key_pem: String,
}
#[derive(Deserialize, Serialize)]
2021-04-09 00:22:17 +00:00
#[serde(rename_all = "camelCase")]
2023-01-06 22:34:50 +00:00
pub struct ActorImage {
2021-04-09 00:22:17 +00:00
#[serde(rename = "type")]
object_type: String,
pub url: String,
pub media_type: Option<String>,
2021-04-09 00:22:17 +00:00
}
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ActorAttachment {
pub name: String,
2021-09-17 12:36:24 +00:00
#[serde(rename = "type")]
pub object_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub href: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub signature_algorithm: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub signature_value: Option<String>,
2021-09-17 12:36:24 +00:00
}
// Some implementations use empty object instead of null
2023-04-24 15:35:32 +00:00
fn deserialize_image_opt<'de, D>(deserializer: D) -> Result<Option<ActorImage>, D::Error>
where
D: Deserializer<'de>,
{
let maybe_value: Option<Value> = Option::deserialize(deserializer)?;
let maybe_image = if let Some(value) = maybe_value {
2023-04-24 15:35:32 +00:00
let is_empty_object = value.as_object().map(|map| map.is_empty()).unwrap_or(false);
if is_empty_object {
None
} else {
2023-04-24 15:35:32 +00:00
let image = ActorImage::deserialize(value).map_err(DeserializerError::custom)?;
Some(image)
}
} else {
None
};
Ok(maybe_image)
}
#[derive(Deserialize, Serialize)]
#[cfg_attr(test, derive(Default))]
2021-04-09 00:22:17 +00:00
#[serde(rename_all = "camelCase")]
pub struct Actor {
#[serde(rename = "@context")]
pub context: Option<Value>,
2021-04-09 00:22:17 +00:00
pub id: String,
#[serde(rename = "type")]
pub object_type: String,
2021-04-09 00:22:17 +00:00
pub name: Option<String>,
2021-04-09 00:22:17 +00:00
pub preferred_username: String,
pub inbox: String,
pub outbox: String,
2021-11-18 00:51:56 +00:00
2023-04-26 10:55:42 +00:00
#[serde(default)]
2023-04-25 11:19:04 +00:00
pub bot: bool,
2021-11-18 00:51:56 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
pub followers: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub following: Option<String>,
2021-04-09 00:22:17 +00:00
2022-06-14 23:41:01 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
pub subscribers: Option<String>,
2021-04-09 00:22:17 +00:00
pub public_key: PublicKey,
#[serde(
default,
deserialize_with = "deserialize_image_opt",
2023-04-24 15:35:32 +00:00
skip_serializing_if = "Option::is_none"
)]
2023-01-06 22:34:50 +00:00
pub icon: Option<ActorImage>,
2021-04-09 00:22:17 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
2023-01-06 22:34:50 +00:00
pub image: Option<ActorImage>,
2021-04-09 00:22:17 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
2021-09-17 12:36:24 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
pub also_known_as: Option<Value>,
#[serde(
default,
deserialize_with = "deserialize_value_array",
2023-04-24 15:35:32 +00:00
skip_serializing_if = "Vec::is_empty"
)]
pub attachment: Vec<Value>,
#[serde(default)]
pub manually_approves_followers: bool,
2023-03-03 22:08:38 +00:00
#[serde(
default,
deserialize_with = "deserialize_value_array",
2023-04-24 15:35:32 +00:00
skip_serializing_if = "Vec::is_empty"
2023-03-03 22:08:38 +00:00
)]
pub tag: Vec<Value>,
2023-01-07 20:33:23 +00:00
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
2021-09-17 12:36:24 +00:00
}
impl Actor {
2023-04-24 15:35:32 +00:00
pub fn address(&self) -> Result<ActorAddress, ValidationError> {
2023-04-26 10:55:42 +00:00
let hostname =
get_hostname(&self.id).map_err(|_| ValidationError("invalid actor ID".to_string()))?;
let actor_address = ActorAddress {
username: self.preferred_username.clone(),
hostname: hostname,
};
Ok(actor_address)
}
pub fn into_db_actor(self) -> DbActor {
DbActor {
object_type: self.object_type,
id: self.id,
inbox: self.inbox,
outbox: self.outbox,
followers: self.followers,
subscribers: self.subscribers,
url: self.url,
public_key: DbActorPublicKey {
id: self.public_key.id,
owner: self.public_key.owner,
public_key_pem: self.public_key.public_key_pem,
},
}
}
2023-04-24 15:35:32 +00:00
pub fn parse_attachments(&self) -> (Vec<IdentityProof>, Vec<PaymentOption>, Vec<ExtraField>) {
let mut identity_proofs = vec![];
let mut payment_options = vec![];
let mut extra_fields = vec![];
let log_error = |attachment_type: &str, error| {
log::warn!(
"ignoring actor attachment of type {}: {}",
attachment_type,
error,
);
};
for attachment_value in self.attachment.iter() {
2023-04-24 15:35:32 +00:00
let attachment_type = attachment_value["type"].as_str().unwrap_or("Unknown");
let attachment = match serde_json::from_value(attachment_value.clone()) {
Ok(attachment) => attachment,
Err(_) => {
2023-04-26 10:55:42 +00:00
log_error(
attachment_type,
ValidationError("invalid attachment".to_string()),
);
continue;
2023-04-24 15:35:32 +00:00
}
};
match attachment_type {
IDENTITY_PROOF => {
match parse_identity_proof(&self.id, &attachment) {
Ok(proof) => identity_proofs.push(proof),
Err(error) => log_error(attachment_type, error),
};
2023-04-24 15:35:32 +00:00
}
LINK => {
match parse_payment_option(&attachment) {
Ok(option) => payment_options.push(option),
Err(error) => log_error(attachment_type, error),
};
2023-04-24 15:35:32 +00:00
}
PROPERTY_VALUE => {
match parse_extra_field(&attachment) {
Ok(field) => extra_fields.push(field),
Err(error) => log_error(attachment_type, error),
};
2023-04-24 15:35:32 +00:00
}
_ => {
log_error(
attachment_type,
2023-04-26 10:55:42 +00:00
ValidationError("unsupported attachment type".to_string()),
);
2023-04-24 15:35:32 +00:00
}
};
2023-04-24 15:35:32 +00:00
}
(identity_proofs, payment_options, extra_fields)
2021-09-17 12:36:24 +00:00
}
2021-04-09 00:22:17 +00:00
}
2021-11-12 23:41:03 +00:00
pub type ActorKeyError = rsa::pkcs8::Error;
fn build_actor_context() -> (
&'static str,
&'static str,
HashMap<&'static str, &'static str>,
) {
(
AP_CONTEXT,
W3ID_SECURITY_CONTEXT,
HashMap::from([
("manuallyApprovesFollowers", "as:manuallyApprovesFollowers"),
("schema", SCHEMA_ORG_CONTEXT),
("PropertyValue", "schema:PropertyValue"),
("value", "schema:value"),
("toot", MASTODON_CONTEXT),
("IdentityProof", "toot:IdentityProof"),
2023-04-25 13:49:35 +00:00
("fedimovies", MITRA_CONTEXT),
("subscribers", "fedimovies:subscribers"),
]),
)
}
2023-04-24 15:35:32 +00:00
pub fn get_local_actor(user: &User, instance_url: &str) -> Result<Actor, ActorKeyError> {
2021-04-09 00:22:17 +00:00
let username = &user.profile.username;
let actor_id = local_actor_id(instance_url, username);
let inbox = LocalActorCollection::Inbox.of(&actor_id);
let outbox = LocalActorCollection::Outbox.of(&actor_id);
let followers = LocalActorCollection::Followers.of(&actor_id);
let following = LocalActorCollection::Following.of(&actor_id);
let subscribers = LocalActorCollection::Subscribers.of(&actor_id);
2021-04-09 00:22:17 +00:00
2021-09-17 12:36:24 +00:00
let private_key = deserialize_private_key(&user.private_key)?;
let public_key_pem = get_public_key_pem(&private_key)?;
2021-04-09 00:22:17 +00:00
let public_key = PublicKey {
id: local_actor_key_id(&actor_id),
owner: actor_id.clone(),
2021-04-09 00:22:17 +00:00
public_key_pem: public_key_pem,
};
let avatar = match &user.profile.avatar {
Some(image) => {
2023-01-06 22:34:50 +00:00
let actor_image = ActorImage {
2021-04-09 00:22:17 +00:00
object_type: IMAGE.to_string(),
url: get_file_url(instance_url, &image.file_name),
media_type: image.media_type.clone(),
2021-04-09 00:22:17 +00:00
};
2023-01-06 22:34:50 +00:00
Some(actor_image)
2023-04-24 15:35:32 +00:00
}
2021-04-09 00:22:17 +00:00
None => None,
};
let banner = match &user.profile.banner {
Some(image) => {
2023-01-06 22:34:50 +00:00
let actor_image = ActorImage {
2021-04-09 00:22:17 +00:00
object_type: IMAGE.to_string(),
url: get_file_url(instance_url, &image.file_name),
media_type: image.media_type.clone(),
2021-04-09 00:22:17 +00:00
};
2023-01-06 22:34:50 +00:00
Some(actor_image)
2023-04-24 15:35:32 +00:00
}
2021-04-09 00:22:17 +00:00
None => None,
};
let mut attachments = vec![];
for proof in user.profile.identity_proofs.clone().into_inner() {
let attachment = attach_identity_proof(proof);
2023-04-24 15:35:32 +00:00
let attachment_value =
serde_json::to_value(attachment).expect("attachment should be serializable");
attachments.push(attachment_value);
2023-04-24 15:35:32 +00:00
}
for payment_option in user.profile.payment_options.clone().into_inner() {
2023-04-24 15:35:32 +00:00
let attachment =
attach_payment_option(instance_url, &user.profile.username, payment_option);
let attachment_value =
serde_json::to_value(attachment).expect("attachment should be serializable");
attachments.push(attachment_value);
2023-04-24 15:35:32 +00:00
}
for field in user.profile.extra_fields.clone().into_inner() {
let attachment = attach_extra_field(field);
2023-04-24 15:35:32 +00:00
let attachment_value =
serde_json::to_value(attachment).expect("attachment should be serializable");
attachments.push(attachment_value);
2023-04-24 15:35:32 +00:00
}
let aliases = user.profile.aliases.clone().into_actor_ids();
2021-04-09 00:22:17 +00:00
let actor = Actor {
2023-04-25 13:49:35 +00:00
context: Some(build_default_context()),
id: actor_id.clone(),
2021-04-09 00:22:17 +00:00
object_type: PERSON.to_string(),
name: user.profile.display_name.clone(),
2021-04-09 00:22:17 +00:00
preferred_username: username.to_string(),
inbox,
outbox,
2023-04-25 11:19:04 +00:00
bot: true,
2021-11-18 00:51:56 +00:00
followers: Some(followers),
following: Some(following),
2022-06-14 23:41:01 +00:00
subscribers: Some(subscribers),
2021-04-09 00:22:17 +00:00
public_key,
icon: avatar,
image: banner,
summary: user.profile.bio.clone(),
also_known_as: Some(json!(aliases)),
attachment: attachments,
manually_approves_followers: false,
2023-03-03 22:08:38 +00:00
tag: vec![],
url: Some(actor_id),
2021-04-09 00:22:17 +00:00
};
Ok(actor)
}
2021-11-18 00:51:56 +00:00
2023-04-24 15:35:32 +00:00
pub fn get_instance_actor(instance: &Instance) -> Result<Actor, ActorKeyError> {
let actor_id = local_instance_actor_id(&instance.url());
2022-07-15 19:36:21 +00:00
let actor_inbox = LocalActorCollection::Inbox.of(&actor_id);
let actor_outbox = LocalActorCollection::Outbox.of(&actor_id);
2021-11-18 00:51:56 +00:00
let public_key_pem = get_public_key_pem(&instance.actor_key)?;
let public_key = PublicKey {
id: local_actor_key_id(&actor_id),
2021-11-18 00:51:56 +00:00
owner: actor_id.clone(),
public_key_pem: public_key_pem,
};
let actor = Actor {
2023-04-25 13:49:35 +00:00
context: Some(build_default_context()),
2021-11-18 00:51:56 +00:00
id: actor_id,
object_type: SERVICE.to_string(),
name: Some(instance.hostname()),
preferred_username: instance.hostname(),
2021-11-18 00:51:56 +00:00
inbox: actor_inbox,
outbox: actor_outbox,
2023-04-25 11:19:04 +00:00
bot: true,
2021-11-18 00:51:56 +00:00
followers: None,
following: None,
2022-06-14 23:41:01 +00:00
subscribers: None,
2021-11-18 00:51:56 +00:00
public_key,
icon: None,
image: None,
summary: None,
also_known_as: None,
attachment: vec![],
manually_approves_followers: false,
2023-03-03 22:08:38 +00:00
tag: vec![],
url: None,
2021-11-18 00:51:56 +00:00
};
Ok(actor)
}
2022-04-26 22:35:39 +00:00
#[cfg(test)]
mod tests {
use super::*;
2023-04-25 13:49:35 +00:00
use fedimovies_models::profiles::types::DbActorProfile;
use fedimovies_utils::crypto_rsa::{generate_weak_rsa_key, serialize_private_key};
2022-04-26 22:35:39 +00:00
const INSTANCE_HOSTNAME: &str = "example.com";
2022-04-26 22:35:39 +00:00
const INSTANCE_URL: &str = "https://example.com";
#[test]
fn test_get_actor_address() {
let actor = Actor {
id: "https://test.org/users/1".to_string(),
preferred_username: "test".to_string(),
..Default::default()
};
2022-10-03 21:21:20 +00:00
let actor_address = actor.address().unwrap();
assert_eq!(actor_address.acct(INSTANCE_HOSTNAME), "test@test.org");
}
2022-04-26 22:35:39 +00:00
#[test]
fn test_local_actor() {
let private_key = generate_weak_rsa_key().unwrap();
let private_key_pem = serialize_private_key(&private_key).unwrap();
2022-04-26 22:35:39 +00:00
let profile = DbActorProfile {
username: "testuser".to_string(),
bio: Some("testbio".to_string()),
2022-04-26 22:35:39 +00:00
..Default::default()
};
let user = User {
private_key: private_key_pem,
profile,
..Default::default()
};
let actor = get_local_actor(&user, INSTANCE_URL).unwrap();
assert_eq!(actor.id, "https://example.com/users/testuser");
assert_eq!(actor.preferred_username, user.profile.username);
2022-07-15 19:57:55 +00:00
assert_eq!(actor.inbox, "https://example.com/users/testuser/inbox");
assert_eq!(actor.outbox, "https://example.com/users/testuser/outbox");
assert_eq!(
actor.followers.unwrap(),
"https://example.com/users/testuser/followers",
);
assert_eq!(
actor.following.unwrap(),
"https://example.com/users/testuser/following",
);
assert_eq!(
actor.subscribers.unwrap(),
"https://example.com/users/testuser/subscribers",
);
assert_eq!(
actor.public_key.id,
"https://example.com/users/testuser#main-key",
);
assert_eq!(actor.attachment.len(), 0);
assert_eq!(actor.summary, user.profile.bio);
2022-04-26 22:35:39 +00:00
}
2022-07-15 19:57:55 +00:00
#[test]
fn test_instance_actor() {
2023-01-12 21:38:36 +00:00
let instance_url = "https://example.com/";
let instance = Instance::for_test(instance_url);
2022-07-15 19:57:55 +00:00
let actor = get_instance_actor(&instance).unwrap();
assert_eq!(actor.id, "https://example.com/actor");
assert_eq!(actor.object_type, "Service");
assert_eq!(actor.preferred_username, "example.com");
assert_eq!(actor.inbox, "https://example.com/actor/inbox");
assert_eq!(actor.outbox, "https://example.com/actor/outbox");
assert_eq!(actor.public_key.id, "https://example.com/actor#main-key");
}
2022-04-26 22:35:39 +00:00
}