]> Untitled Git - lemmy.git/blobdiff - server/src/apub/user.rs
Merge branch 'main' into federation-authorisation
[lemmy.git] / server / src / apub / user.rs
index c840cc22d88b149c220f9b4c2f13f11b7183bf00..58338ab4da3c93d77a25e6679fd9724af7fd9c39 100644 (file)
 use crate::{
+  api::{check_slurs, check_slurs_opt},
   apub::{
-    activities::send_activity,
+    activities::{generate_activity_id, send_activity},
+    check_actor_domain,
     create_apub_response,
-    extensions::signatures::PublicKey,
+    insert_activity,
     ActorType,
     FromApub,
     PersonExt,
     ToApub,
   },
-  convert_datetime,
-  db::{
-    activity::insert_activity,
-    user::{UserForm, User_},
-  },
-  naive_now,
+  blocking,
   routes::DbPoolParam,
+  DbPool,
+  LemmyError,
 };
 use activitystreams::{
-  actor::{properties::ApActorProperties, Person},
+  activity::{
+    kind::{FollowType, UndoType},
+    Follow,
+    Undo,
+  },
+  actor::{ApActor, Endpoints, Person},
   context,
-  endpoint::EndpointProperties,
-  object::{properties::ObjectProperties, AnyImage, Image},
-  primitives::XsdAnyUri,
-};
-use activitystreams_ext::Ext2;
-use activitystreams_new::{
-  activity::{Follow, Undo},
-  object::Tombstone,
+  object::{Image, Tombstone},
   prelude::*,
 };
-use actix_web::{body::Body, web::Path, HttpResponse, Result};
-use diesel::PgConnection;
-use failure::Error;
+use activitystreams_ext::Ext1;
+use actix_web::{body::Body, client::Client, web, HttpResponse};
+use lemmy_db::{
+  naive_now,
+  user::{UserForm, User_},
+};
+use lemmy_utils::convert_datetime;
 use serde::Deserialize;
+use url::Url;
 
 #[derive(Deserialize)]
 pub struct UserQuery {
   user_name: String,
 }
 
+#[async_trait::async_trait(?Send)]
 impl ToApub for User_ {
   type Response = PersonExt;
 
   // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network.
-  fn to_apub(&self, _conn: &PgConnection) -> Result<PersonExt, Error> {
+  async fn to_apub(&self, _pool: &DbPool) -> Result<PersonExt, LemmyError> {
     // TODO go through all these to_string and to_owned()
-    let mut person = Person::default();
-    let oprops: &mut ObjectProperties = person.as_mut();
-    oprops
-      .set_context_xsd_any_uri(context())?
-      .set_id(self.actor_id.to_string())?
-      .set_name_xsd_string(self.name.to_owned())?
-      .set_published(convert_datetime(self.published))?;
+    let mut person = Person::new();
+    person
+      .set_context(context())
+      .set_id(Url::parse(&self.actor_id)?)
+      .set_name(self.name.to_owned())
+      .set_published(convert_datetime(self.published));
 
     if let Some(u) = self.updated {
-      oprops.set_updated(convert_datetime(u))?;
-    }
-
-    if let Some(i) = &self.preferred_username {
-      oprops.set_name_xsd_string(i.to_owned())?;
+      person.set_updated(convert_datetime(u));
     }
 
     if let Some(avatar_url) = &self.avatar {
       let mut image = Image::new();
-      image
-        .object_props
-        .set_url_xsd_any_uri(avatar_url.to_owned())?;
-      let any_image = AnyImage::from_concrete(image)?;
-      oprops.set_icon_any_image(any_image)?;
+      image.set_url(avatar_url.to_owned());
+      person.set_icon(image.into_any_base()?);
     }
 
-    let mut endpoint_props = EndpointProperties::default();
+    if let Some(banner_url) = &self.banner {
+      let mut image = Image::new();
+      image.set_url(banner_url.to_owned());
+      person.set_image(image.into_any_base()?);
+    }
 
-    endpoint_props.set_shared_inbox(self.get_shared_inbox_url())?;
+    if let Some(bio) = &self.bio {
+      person.set_summary(bio.to_owned());
+    }
 
-    let mut actor_props = ApActorProperties::default();
+    let mut ap_actor = ApActor::new(self.get_inbox_url()?, person);
+    ap_actor
+      .set_outbox(self.get_outbox_url()?)
+      .set_followers(self.get_followers_url().parse()?)
+      .set_following(self.get_following_url().parse()?)
+      .set_liked(self.get_liked_url().parse()?)
+      .set_endpoints(Endpoints {
+        shared_inbox: Some(self.get_shared_inbox_url().parse()?),
+        ..Default::default()
+      });
 
-    actor_props
-      .set_inbox(self.get_inbox_url())?
-      .set_outbox(self.get_outbox_url())?
-      .set_endpoints(endpoint_props)?
-      .set_followers(self.get_followers_url())?
-      .set_following(self.get_following_url())?
-      .set_liked(self.get_liked_url())?;
+    if let Some(i) = &self.preferred_username {
+      ap_actor.set_preferred_username(i.to_owned());
+    }
 
-    Ok(Ext2::new(person, actor_props, self.get_public_key_ext()))
+    Ok(Ext1::new(ap_actor, self.get_public_key_ext()))
   }
-  fn to_tombstone(&self) -> Result<Tombstone, Error> {
+  fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
     unimplemented!()
   }
 }
 
+#[async_trait::async_trait(?Send)]
 impl ActorType for User_ {
-  fn actor_id(&self) -> String {
+  fn actor_id_str(&self) -> String {
     self.actor_id.to_owned()
   }
 
@@ -105,91 +112,166 @@ impl ActorType for User_ {
   }
 
   /// As a given local user, send out a follow request to a remote community.
-  fn send_follow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error> {
-    let id = format!("{}/follow/{}", self.actor_id, uuid::Uuid::new_v4());
+  async fn send_follow(
+    &self,
+    follow_actor_id: &str,
+    client: &Client,
+    pool: &DbPool,
+  ) -> Result<(), LemmyError> {
     let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id);
-    follow.set_context(context()).set_id(id.parse()?);
+    follow
+      .set_context(context())
+      .set_id(generate_activity_id(FollowType::Follow)?);
     let to = format!("{}/inbox", follow_actor_id);
 
-    insert_activity(&conn, self.id, &follow, true)?;
+    insert_activity(self.id, follow.clone(), true, pool).await?;
 
-    send_activity(&follow, self, vec![to])?;
+    send_activity(client, &follow.into_any_base()?, self, vec![to]).await?;
     Ok(())
   }
 
-  fn send_unfollow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error> {
-    let id = format!("{}/follow/{}", self.actor_id, uuid::Uuid::new_v4());
+  async fn send_unfollow(
+    &self,
+    follow_actor_id: &str,
+    client: &Client,
+    pool: &DbPool,
+  ) -> Result<(), LemmyError> {
     let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id);
-    follow.set_context(context()).set_id(id.parse()?);
+    follow
+      .set_context(context())
+      .set_id(generate_activity_id(FollowType::Follow)?);
 
     let to = format!("{}/inbox", follow_actor_id);
 
-    // TODO
     // Undo that fake activity
-    let undo_id = format!("{}/undo/follow/{}", self.actor_id, uuid::Uuid::new_v4());
-    let mut undo = Undo::new(self.actor_id.parse::<XsdAnyUri>()?, follow.into_any_base()?);
-    undo.set_context(context()).set_id(undo_id.parse()?);
+    let mut undo = Undo::new(Url::parse(&self.actor_id)?, follow.into_any_base()?);
+    undo
+      .set_context(context())
+      .set_id(generate_activity_id(UndoType::Undo)?);
 
-    insert_activity(&conn, self.id, &undo, true)?;
+    insert_activity(self.id, undo.clone(), true, pool).await?;
 
-    send_activity(&undo, self, vec![to])?;
+    send_activity(client, &undo.into_any_base()?, self, vec![to]).await?;
     Ok(())
   }
 
-  fn send_delete(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> {
+  async fn send_delete(
+    &self,
+    _creator: &User_,
+    _client: &Client,
+    _pool: &DbPool,
+  ) -> Result<(), LemmyError> {
     unimplemented!()
   }
 
-  fn send_undo_delete(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> {
+  async fn send_undo_delete(
+    &self,
+    _creator: &User_,
+    _client: &Client,
+    _pool: &DbPool,
+  ) -> Result<(), LemmyError> {
     unimplemented!()
   }
 
-  fn send_remove(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> {
+  async fn send_remove(
+    &self,
+    _creator: &User_,
+    _client: &Client,
+    _pool: &DbPool,
+  ) -> Result<(), LemmyError> {
     unimplemented!()
   }
 
-  fn send_undo_remove(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> {
+  async fn send_undo_remove(
+    &self,
+    _creator: &User_,
+    _client: &Client,
+    _pool: &DbPool,
+  ) -> Result<(), LemmyError> {
     unimplemented!()
   }
 
-  fn send_accept_follow(&self, _follow: &Follow, _conn: &PgConnection) -> Result<(), Error> {
+  async fn send_accept_follow(
+    &self,
+    _follow: Follow,
+    _client: &Client,
+    _pool: &DbPool,
+  ) -> Result<(), LemmyError> {
     unimplemented!()
   }
 
-  fn get_follower_inboxes(&self, _conn: &PgConnection) -> Result<Vec<String>, Error> {
+  async fn get_follower_inboxes(&self, _pool: &DbPool) -> Result<Vec<String>, LemmyError> {
     unimplemented!()
   }
+
+  fn user_id(&self) -> i32 {
+    self.id
+  }
 }
 
+#[async_trait::async_trait(?Send)]
 impl FromApub for UserForm {
   type ApubType = PersonExt;
   /// Parse an ActivityPub person received from another instance into a Lemmy user.
-  fn from_apub(person: &PersonExt, _conn: &PgConnection) -> Result<Self, Error> {
-    let oprops = &person.inner.object_props;
-    let aprops = &person.ext_one;
-    let public_key: &PublicKey = &person.ext_two.public_key;
-
-    let avatar = match oprops.get_icon_any_image() {
-      Some(any_image) => any_image
-        .to_owned()
-        .into_concrete::<Image>()?
-        .object_props
-        .get_url_xsd_any_uri()
-        .map(|u| u.to_string()),
+  async fn from_apub(
+    person: &PersonExt,
+    _: &Client,
+    _: &DbPool,
+    expected_domain: Option<Url>,
+  ) -> Result<Self, LemmyError> {
+    let avatar = match person.icon() {
+      Some(any_image) => Some(
+        Image::from_any_base(any_image.as_one().unwrap().clone())
+          .unwrap()
+          .unwrap()
+          .url()
+          .unwrap()
+          .as_single_xsd_any_uri()
+          .map(|u| u.to_string()),
+      ),
       None => None,
     };
 
+    let banner = match person.image() {
+      Some(any_image) => Some(
+        Image::from_any_base(any_image.as_one().unwrap().clone())
+          .unwrap()
+          .unwrap()
+          .url()
+          .unwrap()
+          .as_single_xsd_any_uri()
+          .map(|u| u.to_string()),
+      ),
+      None => None,
+    };
+
+    let name = person
+      .name()
+      .unwrap()
+      .one()
+      .unwrap()
+      .as_xsd_string()
+      .unwrap()
+      .to_string();
+    let preferred_username = person.inner.preferred_username().map(|u| u.to_string());
+    let bio = person
+      .inner
+      .summary()
+      .map(|s| s.as_single_xsd_string().unwrap().into());
+    check_slurs(&name)?;
+    check_slurs_opt(&preferred_username)?;
+    check_slurs_opt(&bio)?;
+
     Ok(UserForm {
-      name: oprops.get_name_xsd_string().unwrap().to_string(),
-      preferred_username: aprops.get_preferred_username().map(|u| u.to_string()),
+      name,
+      preferred_username,
       password_encrypted: "".to_string(),
       admin: false,
       banned: false,
       email: None,
       avatar,
-      updated: oprops
-        .get_updated()
-        .map(|u| u.as_ref().to_owned().naive_local()),
+      banner,
+      updated: person.updated().map(|u| u.to_owned().naive_local()),
       show_nsfw: false,
       theme: "".to_string(),
       default_sort_type: 0,
@@ -198,11 +280,11 @@ impl FromApub for UserForm {
       show_avatars: false,
       send_notifications_to_email: false,
       matrix_user_id: None,
-      actor_id: oprops.get_id().unwrap().to_string(),
-      bio: oprops.get_summary_xsd_string().map(|s| s.to_string()),
+      actor_id: check_actor_domain(person, expected_domain)?,
+      bio,
       local: false,
       private_key: None,
-      public_key: Some(public_key.to_owned().public_key_pem),
+      public_key: Some(person.ext_one.public_key.to_owned().public_key_pem),
       last_refreshed_at: Some(naive_now()),
     })
   }
@@ -210,10 +292,14 @@ impl FromApub for UserForm {
 
 /// Return the user json over HTTP.
 pub async fn get_apub_user_http(
-  info: Path<UserQuery>,
+  info: web::Path<UserQuery>,
   db: DbPoolParam,
-) -> Result<HttpResponse<Body>, Error> {
-  let user = User_::find_by_email_or_username(&&db.get()?, &info.user_name)?;
-  let u = user.to_apub(&db.get().unwrap())?;
+) -> Result<HttpResponse<Body>, LemmyError> {
+  let user_name = info.into_inner().user_name;
+  let user = blocking(&db, move |conn| {
+    User_::find_by_email_or_username(conn, &user_name)
+  })
+  .await??;
+  let u = user.to_apub(&db).await?;
   Ok(create_apub_response(&u))
 }