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::handlers::{
create_note::handle_note,
update_person::update_actor,
update_person::update_remote_profile,
};
use crate::activitypub::receiver::parse_object_id;
use crate::config::{Config, Instance};
@ -96,7 +96,12 @@ pub async fn get_or_import_profile_by_actor_id(
if profile.possibly_outdated() {
let actor = fetch_actor(instance, actor_id).await?;
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
} else {
profile
@ -104,15 +109,33 @@ pub async fn get_or_import_profile_by_actor_id(
},
Err(DatabaseError::NotFound(_)) => {
let actor = fetch_actor(instance, actor_id).await?;
let mut profile_data = prepare_remote_profile_data(
instance,
media_dir,
actor,
).await?;
log::info!("fetched profile {}", profile_data.acct);
profile_data.clean()?;
let profile = create_profile(db_client, profile_data).await?;
profile
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(
instance,
media_dir,
actor,
).await?;
log::info!("fetched profile {}", profile_data.acct);
profile_data.clean()?;
let profile = create_profile(db_client, profile_data).await?;
profile
},
Err(other_error) => return Err(other_error.into()),
}
},
Err(other_error) => return Err(other_error.into()),
};

View file

@ -170,10 +170,8 @@ pub async fn handle_note(
};
continue;
};
// WARNING: `href` attribute is usually actor ID
// NOTE: `href` attribute is usually actor ID
// 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(
db_client,
instance,

View file

@ -25,6 +25,7 @@ pub async fn handle_undo_follow(
let target_actor_id = object.object
.ok_or(ValidationError("invalid object"))?;
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?;
match unfollow(db_client, &source_profile.id, &target_profile.id).await {
Ok(_) => (),

View file

@ -27,16 +27,18 @@ pub async fn handle_update_person(
if actor.id != activity.actor {
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))
}
pub async fn update_actor(
/// Updates remote actor's profile
pub async fn update_remote_profile(
db_client: &impl GenericClient,
media_dir: &Path,
profile: DbActorProfile,
actor: Actor,
) -> 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)?;
if actor_old.id != actor.id {
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_person::prepare_delete_person;
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::database::create_database_client;
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::models::attachments::queries::delete_unused_attachments;
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::{
create_invite_code,
get_invite_codes,
@ -163,8 +167,9 @@ async fn main() {
},
SubCommand::RefetchActor(subopts) => {
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();
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");
},
SubCommand::DeleteProfile(subopts) => {

View file

@ -82,6 +82,13 @@ pub struct DbActorProfile {
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 {
pub fn is_local(&self) -> bool {
self.actor_json.is_none()