inbox_handler.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  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. // Already deleted? (By some admin, perhaps?)
  212. try {
  213. $found = Deleted_notice::getByUri($object);
  214. $deleted = ($found instanceof Deleted_notice);
  215. } catch (NoResultException $e) {
  216. $deleted = false;
  217. }
  218. if (!$deleted) {
  219. $notice = ActivityPubPlugin::grab_notice_from_url($object);
  220. $notice->deleteAs($this->actor);
  221. }
  222. }
  223. /**
  224. * Handles a Follow Activity received by our inbox.
  225. *
  226. * @throws AlreadyFulfilledException
  227. * @throws HTTP_Request2_Exception
  228. * @throws NoProfileException
  229. * @throws ServerException
  230. * @throws \GuzzleHttp\Exception\GuzzleException
  231. * @throws \HttpSignatures\Exception
  232. * @author Diogo Cordeiro <diogo@fc.up.pt>
  233. */
  234. private function handle_follow()
  235. {
  236. Activitypub_follow::follow($this->actor, $this->object, $this->activity['id']);
  237. }
  238. /**
  239. * Handles a Like Activity received by our inbox.
  240. *
  241. * @throws Exception
  242. * @author Diogo Cordeiro <diogo@fc.up.pt>
  243. */
  244. private function handle_like()
  245. {
  246. $notice = ActivityPubPlugin::grab_notice_from_url($this->object);
  247. Fave::addNew($this->actor, $notice);
  248. }
  249. /**
  250. * Handles a Undo Activity received by our inbox.
  251. *
  252. * @throws AlreadyFulfilledException
  253. * @throws HTTP_Request2_Exception
  254. * @throws NoProfileException
  255. * @throws ServerException
  256. * @author Diogo Cordeiro <diogo@fc.up.pt>
  257. */
  258. private function handle_undo()
  259. {
  260. switch ($this->object['type']) {
  261. case 'Follow':
  262. $this->handle_undo_follow();
  263. break;
  264. case 'Like':
  265. $this->handle_undo_like();
  266. break;
  267. }
  268. }
  269. /**
  270. * Handles a Undo Follow Activity received by our inbox.
  271. *
  272. * @throws AlreadyFulfilledException
  273. * @throws HTTP_Request2_Exception
  274. * @throws NoProfileException
  275. * @throws ServerException
  276. * @author Diogo Cordeiro <diogo@fc.up.pt>
  277. */
  278. private function handle_undo_follow()
  279. {
  280. // Get Object profile
  281. $object_profile = new Activitypub_explorer;
  282. $object_profile = $object_profile->lookup($this->object['object'])[0];
  283. if (Subscription::exists($this->actor, $object_profile)) {
  284. Subscription::cancel($this->actor, $object_profile);
  285. // You are no longer following this person.
  286. Activitypub_profile::unsubscribeCacheUpdate($this->actor, $object_profile);
  287. } else {
  288. // 409: You are not following this person already.
  289. }
  290. }
  291. /**
  292. * Handles a Undo Like Activity received by our inbox.
  293. *
  294. * @throws AlreadyFulfilledException
  295. * @throws ServerException
  296. * @author Diogo Cordeiro <diogo@fc.up.pt>
  297. */
  298. private function handle_undo_like()
  299. {
  300. $notice = ActivityPubPlugin::grab_notice_from_url($this->object['object']);
  301. Fave::removeEntry($this->actor, $notice);
  302. }
  303. /**
  304. * Handles a Announce Activity received by our inbox.
  305. *
  306. * @throws Exception
  307. * @author Diogo Cordeiro <diogo@fc.up.pt>
  308. */
  309. private function handle_announce()
  310. {
  311. $object_notice = ActivityPubPlugin::grab_notice_from_url($this->object);
  312. $object_notice->repeat($this->actor, 'ActivityPub');
  313. }
  314. }