Save extra fields from remote actors

This commit is contained in:
silverpill 2021-09-17 12:36:24 +00:00
parent 6dec1a5da1
commit 7fad429a8c
8 changed files with 60 additions and 16 deletions

View file

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use serde_json::{json, Value}; use serde_json::{json, Value};
use crate::config::Config; use crate::config::Config;
use crate::errors::HttpError; use crate::models::profiles::types::ExtraField;
use crate::models::users::types::User; use crate::models::users::types::User;
use crate::utils::crypto::{deserialize_private_key, get_public_key_pem}; use crate::utils::crypto::{deserialize_private_key, get_public_key_pem};
use crate::utils::files::get_file_url; use crate::utils::files::get_file_url;
@ -14,7 +14,7 @@ use super::views::{
get_followers_url, get_followers_url,
get_following_url, get_following_url,
}; };
use super::vocabulary::{PERSON, IMAGE}; use super::vocabulary::{PERSON, IMAGE, PROPERTY_VALUE};
const W3ID_CONTEXT: &str = "https://w3id.org/security/v1"; const W3ID_CONTEXT: &str = "https://w3id.org/security/v1";
@ -40,6 +40,14 @@ pub struct ActorCapabilities {
accepts_chat_messages: Option<bool>, accepts_chat_messages: Option<bool>,
} }
#[derive(Deserialize, Serialize)]
pub struct ActorProperty {
name: String,
#[serde(rename = "type")]
object_type: String,
value: String,
}
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Actor { pub struct Actor {
@ -71,12 +79,31 @@ pub struct Actor {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>, pub summary: Option<String>,
pub attachment: Option<Vec<ActorProperty>>,
}
impl Actor {
/// Parse 'attachment' into ExtraField vector
pub fn extra_fields(&self) -> Vec<ExtraField> {
match &self.attachment {
Some(properties) => {
properties.iter()
.map(|prop| ExtraField {
name: prop.name.clone(),
value: prop.value.clone(),
})
.collect()
},
None => vec![],
}
}
} }
pub fn get_actor_object( pub fn get_actor_object(
config: &Config, config: &Config,
user: &User, user: &User,
) -> Result<Actor, HttpError> { ) -> Result<Actor, rsa::pkcs8::Error> {
let username = &user.profile.username; let username = &user.profile.username;
let id = get_actor_url(&config.instance_url(), &username); let id = get_actor_url(&config.instance_url(), &username);
let inbox = get_inbox_url(&config.instance_url(), &username); let inbox = get_inbox_url(&config.instance_url(), &username);
@ -84,15 +111,16 @@ pub fn get_actor_object(
let followers = get_followers_url(&config.instance_url(), &username); let followers = get_followers_url(&config.instance_url(), &username);
let following = get_following_url(&config.instance_url(), &username); let following = get_following_url(&config.instance_url(), &username);
let private_key = deserialize_private_key(&user.private_key) let private_key = deserialize_private_key(&user.private_key)?;
.map_err(|_| HttpError::InternalError)?; let public_key_pem = get_public_key_pem(&private_key)?;
let public_key_pem = get_public_key_pem(&private_key)
.map_err(|_| HttpError::InternalError)?;
let public_key = PublicKey { let public_key = PublicKey {
id: format!("{}#main-key", id), id: format!("{}#main-key", id),
owner: id.clone(), owner: id.clone(),
public_key_pem: public_key_pem, public_key_pem: public_key_pem,
}; };
let capabilities = ActorCapabilities {
accepts_chat_messages: Some(false),
};
let avatar = match &user.profile.avatar_file_name { let avatar = match &user.profile.avatar_file_name {
Some(file_name) => { Some(file_name) => {
let image = Image { let image = Image {
@ -113,9 +141,15 @@ pub fn get_actor_object(
}, },
None => None, None => None,
}; };
let capabilities = ActorCapabilities { let properties = user.profile.extra_fields.clone()
accepts_chat_messages: Some(false), .unpack().into_iter()
}; .map(|field| {
ActorProperty {
object_type: PROPERTY_VALUE.to_string(),
name: field.name,
value: field.value,
}
}).collect();
let actor = Actor { let actor = Actor {
context: Some(json!([ context: Some(json!([
AP_CONTEXT.to_string(), AP_CONTEXT.to_string(),
@ -134,6 +168,7 @@ pub fn get_actor_object(
icon: avatar, icon: avatar,
image: banner, image: banner,
summary: None, summary: None,
attachment: Some(properties),
}; };
Ok(actor) Ok(actor)
} }

View file

@ -92,6 +92,7 @@ pub async fn fetch_profile_by_actor_id(
let actor_value: Value = serde_json::from_str(&actor_json)?; let actor_value: Value = serde_json::from_str(&actor_json)?;
let actor: Actor = serde_json::from_value(actor_value.clone())?; let actor: Actor = serde_json::from_value(actor_value.clone())?;
let (avatar, banner) = fetch_avatar_and_banner(&actor, media_dir).await?; let (avatar, banner) = fetch_avatar_and_banner(&actor, media_dir).await?;
let extra_fields = actor.extra_fields();
let actor_address = format!( let actor_address = format!(
"{}@{}", "{}@{}",
actor.preferred_username, actor.preferred_username,
@ -102,8 +103,9 @@ pub async fn fetch_profile_by_actor_id(
display_name: Some(actor.name), display_name: Some(actor.name),
acct: actor_address, acct: actor_address,
bio: actor.summary, bio: actor.summary,
avatar: avatar, avatar,
banner: banner, banner,
extra_fields,
actor: Some(actor_value), actor: Some(actor_value),
}; };
Ok(profile_data) Ok(profile_data)

View file

@ -146,13 +146,14 @@ pub async fn receive_activity(
let profile = get_profile_by_actor_id(db_client, &actor.id).await?; let profile = get_profile_by_actor_id(db_client, &actor.id).await?;
let (avatar, banner) = fetch_avatar_and_banner(&actor, &config.media_dir()).await let (avatar, banner) = fetch_avatar_and_banner(&actor, &config.media_dir()).await
.map_err(|_| ValidationError("failed to fetch image"))?; .map_err(|_| ValidationError("failed to fetch image"))?;
let extra_fields = actor.extra_fields();
let mut profile_data = ProfileUpdateData { let mut profile_data = ProfileUpdateData {
display_name: Some(actor.name), display_name: Some(actor.name),
bio: actor.summary.clone(), bio: actor.summary.clone(),
bio_source: actor.summary, bio_source: actor.summary,
avatar, avatar,
banner, banner,
extra_fields: vec![], extra_fields,
}; };
profile_data.clean()?; profile_data.clean()?;
update_profile(db_client, &profile.id, profile_data).await?; update_profile(db_client, &profile.id, profile_data).await?;

View file

@ -47,7 +47,8 @@ async fn get_actor(
) -> Result<HttpResponse, HttpError> { ) -> Result<HttpResponse, HttpError> {
let db_client = &**get_database_client(&db_pool).await?; let db_client = &**get_database_client(&db_pool).await?;
let user = get_user_by_name(db_client, &username).await?; let user = get_user_by_name(db_client, &username).await?;
let actor = get_actor_object(&config, &user)?; let actor = get_actor_object(&config, &user)
.map_err(|_| HttpError::InternalError)?;
let response = HttpResponse::Ok() let response = HttpResponse::Ok()
.content_type(ACTIVITY_CONTENT_TYPE) .content_type(ACTIVITY_CONTENT_TYPE)
.json(actor); .json(actor);

View file

@ -12,3 +12,4 @@ pub const PERSON: &str = "Person";
pub const DOCUMENT: &str = "Document"; pub const DOCUMENT: &str = "Document";
pub const IMAGE: &str = "Image"; pub const IMAGE: &str = "Image";
pub const NOTE: &str = "Note"; pub const NOTE: &str = "Note";
pub const PROPERTY_VALUE: &str = "PropertyValue";

View file

@ -15,14 +15,15 @@ pub async fn create_profile(
profile_data: &ProfileCreateData, profile_data: &ProfileCreateData,
) -> Result<DbActorProfile, DatabaseError> { ) -> Result<DbActorProfile, DatabaseError> {
let profile_id = Uuid::new_v4(); let profile_id = Uuid::new_v4();
let extra_fields = ExtraFields(profile_data.extra_fields.clone());
let result = db_client.query_one( let result = db_client.query_one(
" "
INSERT INTO actor_profile ( INSERT INTO actor_profile (
id, username, display_name, acct, bio, bio_source, id, username, display_name, acct, bio, bio_source,
avatar_file_name, banner_file_name, avatar_file_name, banner_file_name, extra_fields,
actor_json actor_json
) )
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
RETURNING actor_profile RETURNING actor_profile
", ",
&[ &[
@ -34,6 +35,7 @@ pub async fn create_profile(
&profile_data.bio, &profile_data.bio,
&profile_data.avatar, &profile_data.avatar,
&profile_data.banner, &profile_data.banner,
&extra_fields,
&profile_data.actor, &profile_data.actor,
], ],
).await; ).await;

View file

@ -74,6 +74,7 @@ pub struct ProfileCreateData {
pub bio: Option<String>, pub bio: Option<String>,
pub avatar: Option<String>, pub avatar: Option<String>,
pub banner: Option<String>, pub banner: Option<String>,
pub extra_fields: Vec<ExtraField>,
pub actor: Option<Value>, pub actor: Option<Value>,
} }

View file

@ -145,6 +145,7 @@ pub async fn create_user(
bio: None, bio: None,
avatar: None, avatar: None,
banner: None, banner: None,
extra_fields: vec![],
actor: None, actor: None,
}; };
let profile = create_profile(&transaction, &profile_data).await?; let profile = create_profile(&transaction, &profile_data).await?;