inbox_handler.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. <?php
  2. // This file is part of GNU social - https://www.gnu.org/software/social
  3. //
  4. // GNU social is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // GNU social is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * ActivityPub implementation for GNU social
  18. *
  19. * @package GNUsocial
  20. * @author Diogo Cordeiro <diogo@fc.up.pt>
  21. * @copyright 2018-2019 Free Software Foundation, Inc http://www.fsf.org
  22. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  23. * @link http://www.gnu.org/software/social/
  24. */
  25. defined('GNUSOCIAL') || die();
  26. /**
  27. * ActivityPub Inbox Handler
  28. *
  29. * @category Plugin
  30. * @package GNUsocial
  31. * @author Diogo Cordeiro <diogo@fc.up.pt>
  32. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  33. */
  34. class Activitypub_inbox_handler
  35. {
  36. private $activity;
  37. private $actor;
  38. private $object;
  39. /**
  40. * Create a Inbox Handler to receive something from someone.
  41. *
  42. * @param array $activity Activity we are receiving
  43. * @param Profile $actor_profile Actor originating the activity
  44. * @throws Exception
  45. * @author Diogo Cordeiro <diogo@fc.up.pt>
  46. */
  47. public function __construct($activity, $actor_profile = null)
  48. {
  49. $this->activity = $activity;
  50. $this->object = $activity['object'];
  51. // Validate Activity
  52. $this->validate_activity();
  53. // Get Actor's Profile
  54. if (!is_null($actor_profile)) {
  55. $this->actor = $actor_profile;
  56. } else {
  57. $this->actor = ActivityPub_explorer::get_profile_from_url($this->activity['actor']);
  58. }
  59. // Handle the Activity
  60. $this->process();
  61. }
  62. /**
  63. * Validates if a given Activity is valid. Throws exception if not.
  64. *
  65. * @author Diogo Cordeiro <diogo@fc.up.pt>
  66. * @throws Exception
  67. */
  68. private function validate_activity()
  69. {
  70. // Activity validation
  71. // Validate data
  72. if (!(isset($this->activity['type']))) {
  73. throw new Exception('Activity Validation Failed: Type was not specified.');
  74. }
  75. if (!isset($this->activity['actor'])) {
  76. throw new Exception('Activity Validation Failed: Actor was not specified.');
  77. }
  78. if (!isset($this->activity['object'])) {
  79. throw new Exception('Activity Validation Failed: Object was not specified.');
  80. }
  81. // Object validation
  82. switch ($this->activity['type']) {
  83. case 'Accept':
  84. Activitypub_accept::validate_object($this->object);
  85. break;
  86. case 'Create':
  87. Activitypub_create::validate_object($this->object);
  88. break;
  89. case 'Delete':
  90. Activitypub_delete::validate_object($this->object);
  91. break;
  92. case 'Follow':
  93. case 'Like':
  94. case 'Announce':
  95. if (!filter_var($this->object, FILTER_VALIDATE_URL)) {
  96. throw new Exception('Object is not a valid Object URI for Activity.');
  97. }
  98. break;
  99. case 'Undo':
  100. Activitypub_undo::validate_object($this->object);
  101. break;
  102. default:
  103. throw new Exception('Unknown Activity Type.');
  104. }
  105. }
  106. /**
  107. * Sends the Activity to proper handler in order to be processed.
  108. *
  109. * @author Diogo Cordeiro <diogo@fc.up.pt>
  110. */
  111. private function process()
  112. {
  113. switch ($this->activity['type']) {
  114. case 'Accept':
  115. $this->handle_accept();
  116. break;
  117. case 'Create':
  118. $this->handle_create();
  119. break;
  120. case 'Delete':
  121. $this->handle_delete();
  122. break;
  123. case 'Follow':
  124. $this->handle_follow();
  125. break;
  126. case 'Like':
  127. $this->handle_like();
  128. break;
  129. case 'Undo':
  130. $this->handle_undo();
  131. break;
  132. case 'Announce':
  133. $this->handle_announce();
  134. break;
  135. }
  136. }
  137. /**
  138. * Handles an Accept Activity received by our inbox.
  139. *
  140. * @throws HTTP_Request2_Exception
  141. * @throws NoProfileException
  142. * @throws ServerException
  143. * @author Diogo Cordeiro <diogo@fc.up.pt>
  144. */
  145. private function handle_accept()
  146. {
  147. switch ($this->object['type']) {
  148. case 'Follow':
  149. $this->handle_accept_follow();
  150. break;
  151. }
  152. }
  153. /**
  154. * Handles an Accept Follow Activity received by our inbox.
  155. *
  156. * @throws HTTP_Request2_Exception
  157. * @throws NoProfileException
  158. * @throws ServerException
  159. * @author Diogo Cordeiro <diogo@fc.up.pt>
  160. */
  161. private function handle_accept_follow()
  162. {
  163. // Get valid Object profile
  164. // Note that, since this an accept_follow, the $object
  165. // profile is actually the actor that followed someone
  166. $object_profile = new Activitypub_explorer;
  167. $object_profile = $object_profile->lookup($this->object['object'])[0];
  168. Activitypub_profile::subscribeCacheUpdate($object_profile, $this->actor);
  169. $pending_list = new Activitypub_pending_follow_requests($object_profile->getID(), $this->actor->getID());
  170. $pending_list->remove();
  171. }
  172. /**
  173. * Handles a Create Activity received by our inbox.
  174. *
  175. * @throws Exception
  176. * @author Diogo Cordeiro <diogo@fc.up.pt>
  177. */
  178. private function handle_create()
  179. {
  180. switch ($this->object['type']) {
  181. case 'Note':
  182. $this->handle_create_note();
  183. break;
  184. }
  185. }
  186. /**
  187. * Handle a Create Note Activity received by our inbox.
  188. *
  189. * @author Bruno Casteleiro <brunoccast@fc.up.pt>
  190. */
  191. private function handle_create_note()
  192. {
  193. if (Activitypub_create::isPrivateNote($this->activity)) {
  194. Activitypub_message::create_message($this->object, $this->actor);
  195. } else {
  196. Activitypub_notice::create_notice($this->object, $this->actor);
  197. }
  198. }
  199. /**
  200. * Handles a Delete Activity received by our inbox.
  201. *
  202. * @throws AuthorizationException
  203. * @author Diogo Cordeiro <diogo@fc.up.pt>
  204. */
  205. private function handle_delete()
  206. {
  207. $object = $this->object;
  208. if (is_array($object)) {
  209. $object = $object['id'];
  210. }
  211. // profile deletion ?
  212. $aprofile = Activitypub_explorer::get_aprofile_by_url($object);
  213. if ($aprofile instanceof Activitypub_profile) {
  214. $this->handle_delete_profile($aprofile);
  215. return;
  216. }
  217. // note deletion ?
  218. try {
  219. $notice = ActivityPubPlugin::grab_notice_from_url($object, false);
  220. if ($notice instanceof Notice) {
  221. $this->handle_delete_note($notice);
  222. }
  223. return;
  224. } catch (Exception $e) {
  225. // either already deleted or not a notice at all
  226. // nothing to do..
  227. }
  228. common_log(LOG_INFO, "Ignoring Delete activity, nothing that we can/need to handle.");
  229. }
  230. /**
  231. * Handles a Delete-Profile Activity.
  232. *
  233. * Note that the actual ap_profile is deleted during the ProfileDeleteRelated event,
  234. * subscribed by ActivityPubPlugin.
  235. *
  236. * @param Activitypub_profile $aprofile remote user being deleted
  237. * @return void
  238. * @author Bruno Casteleiro <brunoccast@fc.up.pt>
  239. */
  240. private function handle_delete_profile(Activitypub_profile $aprofile): void
  241. {
  242. $profile = $aprofile->local_profile();
  243. $profile->delete();
  244. }
  245. /**
  246. * Handles a Delete-Note Activity.
  247. *
  248. * @param Notice $note remote note being deleted
  249. * @return void
  250. * @author Bruno Casteleiro <brunoccast@fc.up.pt>
  251. */
  252. private function handle_delete_note(Notice $note): void
  253. {
  254. $note->deleteAs($this->actor);
  255. }
  256. /**
  257. * Handles a Follow Activity received by our inbox.
  258. *
  259. * @throws AlreadyFulfilledException
  260. * @throws HTTP_Request2_Exception
  261. * @throws NoProfileException
  262. * @throws ServerException
  263. * @throws \GuzzleHttp\Exception\GuzzleException
  264. * @throws \HttpSignatures\Exception
  265. * @author Diogo Cordeiro <diogo@fc.up.pt>
  266. */
  267. private function handle_follow()
  268. {
  269. Activitypub_follow::follow($this->actor, $this->object, $this->activity['id']);
  270. }
  271. /**
  272. * Handles a Like Activity received by our inbox.
  273. *
  274. * @throws Exception
  275. * @author Diogo Cordeiro <diogo@fc.up.pt>
  276. */
  277. private function handle_like()
  278. {
  279. $notice = ActivityPubPlugin::grab_notice_from_url($this->object);
  280. Fave::addNew($this->actor, $notice);
  281. }
  282. /**
  283. * Handles a Undo Activity received by our inbox.
  284. *
  285. * @throws AlreadyFulfilledException
  286. * @throws HTTP_Request2_Exception
  287. * @throws NoProfileException
  288. * @throws ServerException
  289. * @author Diogo Cordeiro <diogo@fc.up.pt>
  290. */
  291. private function handle_undo()
  292. {
  293. switch ($this->object['type']) {
  294. case 'Follow':
  295. $this->handle_undo_follow();
  296. break;
  297. case 'Like':
  298. $this->handle_undo_like();
  299. break;
  300. }
  301. }
  302. /**
  303. * Handles a Undo Follow Activity received by our inbox.
  304. *
  305. * @throws AlreadyFulfilledException
  306. * @throws HTTP_Request2_Exception
  307. * @throws NoProfileException
  308. * @throws ServerException
  309. * @author Diogo Cordeiro <diogo@fc.up.pt>
  310. */
  311. private function handle_undo_follow()
  312. {
  313. // Get Object profile
  314. $object_profile = new Activitypub_explorer;
  315. $object_profile = $object_profile->lookup($this->object['object'])[0];
  316. if (Subscription::exists($this->actor, $object_profile)) {
  317. Subscription::cancel($this->actor, $object_profile);
  318. // You are no longer following this person.
  319. Activitypub_profile::unsubscribeCacheUpdate($this->actor, $object_profile);
  320. } else {
  321. // 409: You are not following this person already.
  322. }
  323. }
  324. /**
  325. * Handles a Undo Like Activity received by our inbox.
  326. *
  327. * @throws AlreadyFulfilledException
  328. * @throws ServerException
  329. * @author Diogo Cordeiro <diogo@fc.up.pt>
  330. */
  331. private function handle_undo_like()
  332. {
  333. $notice = ActivityPubPlugin::grab_notice_from_url($this->object['object']);
  334. Fave::removeEntry($this->actor, $notice);
  335. }
  336. /**
  337. * Handles a Announce Activity received by our inbox.
  338. *
  339. * @throws Exception
  340. * @author Diogo Cordeiro <diogo@fc.up.pt>
  341. */
  342. private function handle_announce()
  343. {
  344. $object_notice = ActivityPubPlugin::grab_notice_from_url($this->object);
  345. $object_notice->repeat($this->actor, 'ActivityPub');
  346. }
  347. }