Try to find profile by acct if couldn't find it by actor ID

This should prevent violation of unique constraint on "acct" column
when importing profile by actor ID.
This commit is contained in:
silverpill 2022-07-09 20:29:42 +00:00
parent 48331382c3
commit cedc6667bd
6 changed files with 56 additions and 20 deletions

View file

@ -7,7 +7,7 @@ use crate::activitypub::activity::Object;
use crate::activitypub::actor::{Actor, ActorAddress}; use crate::activitypub::actor::{Actor, ActorAddress};
use crate::activitypub::handlers::{ use crate::activitypub::handlers::{
create_note::handle_note, create_note::handle_note,
update_person::update_actor, update_person::update_remote_profile,
}; };
use crate::activitypub::receiver::parse_object_id; use crate::activitypub::receiver::parse_object_id;
use crate::config::{Config, Instance}; use crate::config::{Config, Instance};
@ -96,7 +96,12 @@ pub async fn get_or_import_profile_by_actor_id(
if profile.possibly_outdated() { if profile.possibly_outdated() {
let actor = fetch_actor(instance, actor_id).await?; let actor = fetch_actor(instance, actor_id).await?;
log::info!("re-fetched profile {}", profile.acct); log::info!("re-fetched profile {}", profile.acct);
let profile = update_actor(db_client, media_dir, actor).await?; let profile = update_remote_profile(
db_client,
media_dir,
profile,
actor,
).await?;
profile profile
} else { } else {
profile profile
@ -104,6 +109,21 @@ pub async fn get_or_import_profile_by_actor_id(
}, },
Err(DatabaseError::NotFound(_)) => { Err(DatabaseError::NotFound(_)) => {
let actor = fetch_actor(instance, actor_id).await?; let actor = fetch_actor(instance, actor_id).await?;
let actor_address = actor.address(&instance.host())
.map_err(|_| ValidationError("invalid actor ID"))?;
match get_profile_by_acct(db_client, &actor_address.acct()).await {
Ok(profile) => {
// WARNING: Possible actor ID change
log::info!("re-fetched profile {}", profile.acct);
let profile = update_remote_profile(
db_client,
media_dir,
profile,
actor,
).await?;
profile
},
Err(DatabaseError::NotFound(_)) => {
let mut profile_data = prepare_remote_profile_data( let mut profile_data = prepare_remote_profile_data(
instance, instance,
media_dir, media_dir,
@ -115,6 +135,9 @@ pub async fn get_or_import_profile_by_actor_id(
profile profile
}, },
Err(other_error) => return Err(other_error.into()), Err(other_error) => return Err(other_error.into()),
}
},
Err(other_error) => return Err(other_error.into()),
}; };
Ok(profile) Ok(profile)
} }

View file

@ -170,10 +170,8 @@ pub async fn handle_note(
}; };
continue; continue;
}; };
// WARNING: `href` attribute is usually actor ID // NOTE: `href` attribute is usually actor ID
// but also can be actor URL (profile link). // but also can be actor URL (profile link).
// This may lead to failed import due to
// unique constraint violation on DB insert.
match get_or_import_profile_by_actor_id( match get_or_import_profile_by_actor_id(
db_client, db_client,
instance, instance,

View file

@ -25,6 +25,7 @@ pub async fn handle_undo_follow(
let target_actor_id = object.object let target_actor_id = object.object
.ok_or(ValidationError("invalid object"))?; .ok_or(ValidationError("invalid object"))?;
let target_username = parse_actor_id(&config.instance_url(), &target_actor_id)?; let target_username = parse_actor_id(&config.instance_url(), &target_actor_id)?;
// acct equals username if profile is local
let target_profile = get_profile_by_acct(db_client, &target_username).await?; let target_profile = get_profile_by_acct(db_client, &target_username).await?;
match unfollow(db_client, &source_profile.id, &target_profile.id).await { match unfollow(db_client, &source_profile.id, &target_profile.id).await {
Ok(_) => (), Ok(_) => (),

View file

@ -27,16 +27,18 @@ pub async fn handle_update_person(
if actor.id != activity.actor { if actor.id != activity.actor {
return Err(ValidationError("actor ID mismatch").into()); return Err(ValidationError("actor ID mismatch").into());
}; };
update_actor(db_client, media_dir, actor).await?; let profile = get_profile_by_actor_id(db_client, &actor.id).await?;
update_remote_profile(db_client, media_dir, profile, actor).await?;
Ok(Some(PERSON)) Ok(Some(PERSON))
} }
pub async fn update_actor( /// Updates remote actor's profile
pub async fn update_remote_profile(
db_client: &impl GenericClient, db_client: &impl GenericClient,
media_dir: &Path, media_dir: &Path,
profile: DbActorProfile,
actor: Actor, actor: Actor,
) -> Result<DbActorProfile, ImportError> { ) -> Result<DbActorProfile, ImportError> {
let profile = get_profile_by_actor_id(db_client, &actor.id).await?;
let actor_old = profile.actor_json.ok_or(ImportError::LocalObject)?; let actor_old = profile.actor_json.ok_or(ImportError::LocalObject)?;
if actor_old.id != actor.id { if actor_old.id != actor.id {
log::warn!( log::warn!(

View file

@ -6,7 +6,7 @@ use uuid::Uuid;
use mitra::activitypub::builders::delete_note::prepare_delete_note; use mitra::activitypub::builders::delete_note::prepare_delete_note;
use mitra::activitypub::builders::delete_person::prepare_delete_person; use mitra::activitypub::builders::delete_person::prepare_delete_person;
use mitra::activitypub::fetcher::fetchers::fetch_actor; use mitra::activitypub::fetcher::fetchers::fetch_actor;
use mitra::activitypub::handlers::update_person::update_actor; use mitra::activitypub::handlers::update_person::update_remote_profile;
use mitra::config::{parse_config, Config}; use mitra::config::{parse_config, Config};
use mitra::database::create_database_client; use mitra::database::create_database_client;
use mitra::database::migrate::apply_migrations; use mitra::database::migrate::apply_migrations;
@ -16,7 +16,11 @@ use mitra::ethereum::utils::key_to_ethereum_address;
use mitra::logger::configure_logger; use mitra::logger::configure_logger;
use mitra::models::attachments::queries::delete_unused_attachments; use mitra::models::attachments::queries::delete_unused_attachments;
use mitra::models::posts::queries::{delete_post, find_extraneous_posts, get_post_by_id}; use mitra::models::posts::queries::{delete_post, find_extraneous_posts, get_post_by_id};
use mitra::models::profiles::queries::{delete_profile, get_profile_by_id}; use mitra::models::profiles::queries::{
delete_profile,
get_profile_by_actor_id,
get_profile_by_id,
};
use mitra::models::users::queries::{ use mitra::models::users::queries::{
create_invite_code, create_invite_code,
get_invite_codes, get_invite_codes,
@ -163,8 +167,9 @@ async fn main() {
}, },
SubCommand::RefetchActor(subopts) => { SubCommand::RefetchActor(subopts) => {
let actor_id = subopts.id; let actor_id = subopts.id;
let profile = get_profile_by_actor_id(db_client, &actor_id).await.unwrap();
let actor = fetch_actor(&config.instance(), &actor_id).await.unwrap(); let actor = fetch_actor(&config.instance(), &actor_id).await.unwrap();
update_actor(db_client, &config.media_dir(), actor).await.unwrap(); update_remote_profile(db_client, &config.media_dir(), profile, actor).await.unwrap();
println!("profile updated"); println!("profile updated");
}, },
SubCommand::DeleteProfile(subopts) => { SubCommand::DeleteProfile(subopts) => {

View file

@ -82,6 +82,13 @@ pub struct DbActorProfile {
pub actor_id: Option<String>, pub actor_id: Option<String>,
} }
// Profile identifiers:
// id (local profile UUID): never changes
// acct (webfinger): must never change
// actor_id of remote actor: may change if acct remains the same
// actor RSA key: can be updated at any time by the instance admin
// identity proofs: TBD (likely will do "Trust on first use" (TOFU))
impl DbActorProfile { impl DbActorProfile {
pub fn is_local(&self) -> bool { pub fn is_local(&self) -> bool {
self.actor_json.is_none() self.actor_json.is_none()