remotefollowinit.php 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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. * Remote Follow implementation for GNU social
  18. *
  19. * @package GNUsocial
  20. * @author Bruno Casteleiro <brunoccast@fc.up.pt>
  21. * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
  22. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  23. */
  24. defined('GNUSOCIAL') || die();
  25. /**
  26. * Remote-follow preparation action
  27. *
  28. * @category Plugin
  29. * @package GNUsocial
  30. * @author Bruno Casteleiro <brunoccast@fc.up.pt>
  31. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  32. */
  33. class RemoteFollowInitAction extends Action
  34. {
  35. protected $target = null;
  36. protected $profile = null;
  37. protected function prepare(array $args = [])
  38. {
  39. parent::prepare($args);
  40. if (common_logged_in()) {
  41. // TRANS: Client error displayed when the user is logged in.
  42. $this->clientError(_m('You can use the local subscription!'));
  43. }
  44. // Local user the remote wants to follow
  45. $nickname = $this->trimmed('nickname');
  46. $this->target = User::getKV('nickname', $nickname);
  47. if (!$this->target instanceof User) {
  48. // TRANS: Client error displayed when targeting an invalid user.
  49. $this->clientError(_m('No such user.'));
  50. }
  51. // Webfinger or profile URL of the remote user
  52. $this->profile = $this->trimmed('profile');
  53. return true;
  54. }
  55. protected function handle()
  56. {
  57. parent::handle();
  58. if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  59. /* Use a session token for CSRF protection. */
  60. $token = $this->trimmed('token');
  61. if (!$token || $token != common_session_token()) {
  62. // TRANS: Error displayed when the session token does not match or is not given.
  63. $this->showForm(_m('There was a problem with your session token. '.
  64. 'Try again, please.'));
  65. return;
  66. }
  67. $url = null;
  68. if (Event::handle('RemoteFollowConnectProfile', [$this->target, $this->profile, &$url])) {
  69. // use ported ostatus connect functions to find remote url
  70. $url = self::ostatusConnect($this->target, $this->profile);
  71. }
  72. if (!is_null($url)) {
  73. common_redirect($url, 303);
  74. }
  75. // TRANS: Error displayed when there is failure in connecting with the remote profile.
  76. $this->showForm(_m('There was a problem connecting with the remote profile. '.
  77. 'Try again, please.'));
  78. } else {
  79. $this->showForm();
  80. }
  81. }
  82. function showContent()
  83. {
  84. // TRANS: Form legend. %s is a nickname.
  85. $header = sprintf(_m('Subscribe to %s'), $this->target->getNickname());
  86. // TRANS: Button text to subscribe to a profile.
  87. $submit = _m('BUTTON', 'Subscribe');
  88. $this->elementStart('form',
  89. ['id' => 'form_ostatus_connect',
  90. 'method' => 'post',
  91. 'class' => 'form_settings',
  92. 'action' => common_local_url('RemoteFollowInit')]);
  93. $this->elementStart('fieldset');
  94. $this->element('legend', null, $header);
  95. $this->hidden('token', common_session_token());
  96. $this->elementStart('ul', 'form_data');
  97. $this->elementStart('li', ['id' => 'ostatus_nickname']);
  98. $this->input('nickname',
  99. // TRANS: Field label.
  100. _m('User nickname'),
  101. $this->target->getNickname(),
  102. // TRANS: Field title.
  103. _m('Nickname of the user you want to follow.'));
  104. $this->elementEnd('li');
  105. $this->elementStart('li', ['id' => 'ostatus_profile']);
  106. $this->input('profile',
  107. // TRANS: Field label.
  108. _m('Profile Account'),
  109. $this->profile,
  110. // TRANS: Tooltip for field label "Profile Account".
  111. _m('Your account ID (e.g. user@example.com).'));
  112. $this->elementEnd('li');
  113. $this->elementEnd('ul');
  114. $this->submit('submit', $submit);
  115. $this->elementEnd('fieldset');
  116. $this->elementEnd('form');
  117. }
  118. public function showForm($err = null)
  119. {
  120. if ($err) {
  121. $this->error = $err;
  122. }
  123. if ($this->boolean('ajax')) {
  124. $this->startHTML('text/xml;charset=utf-8');
  125. $this->elementStart('head');
  126. // TRANS: Form title.
  127. $this->element('title', null, _m('TITLE','Subscribe to user'));
  128. $this->elementEnd('head');
  129. $this->elementStart('body');
  130. $this->showContent();
  131. $this->elementEnd('body');
  132. $this->endHTML();
  133. } else {
  134. $this->showPage();
  135. }
  136. }
  137. /**
  138. * Find remote url to finish follow interaction
  139. *
  140. * @param User $target local user to be followed
  141. * @param string $remote ID of the remote subscriber
  142. * @return string|null
  143. */
  144. public static function ostatusConnect(User $target, string $remote): ?string
  145. {
  146. $validate = new Validate();
  147. $opts = ['allowed_schemes' => ['http', 'https', 'acct']];
  148. if ($validate->uri($remote, $opts)) {
  149. $bits = parse_url($remote);
  150. if ($bits['scheme'] == 'acct') {
  151. return self::connectWebfinger($bits['path'], $target);
  152. } else {
  153. return self::connectProfile($remote, $target);
  154. }
  155. } else if (strpos($remote, '@') !== false) {
  156. return self::connectWebfinger($remote, $target);
  157. }
  158. common_log(LOG_ERR, 'Must provide a remote profile');
  159. return null;
  160. }
  161. /**
  162. * Find remote url to finish follow interaction from a webfinger ID
  163. *
  164. * @param string $acct
  165. * @param User $target
  166. * @return string|null
  167. * @see ostatusConnect
  168. */
  169. public static function connectWebfinger(string $acct, User $target): ?string
  170. {
  171. $target = common_local_url('userbyid', ['id' => $target->getID()]);
  172. $disco = new Discovery;
  173. $xrd = $disco->lookup($acct);
  174. $link = $xrd->get('http://ostatus.org/schema/1.0/subscribe');
  175. if (!is_null($link)) {
  176. // We found a URL - let's redirect!
  177. if (!empty($link->template)) {
  178. $url = Discovery::applyTemplate($link->template, $target);
  179. } else {
  180. $url = $link->href;
  181. }
  182. common_log(LOG_INFO, "Retrieving url $url for remote subscriber $acct");
  183. return $url;
  184. }
  185. common_log(LOG_ERR, "Could not confirm remote profile $acct");
  186. return null;
  187. }
  188. /**
  189. * Find remote url to finish follow interaction from an url ID
  190. *
  191. * @param string $acct
  192. * @param User $target
  193. * @return string
  194. * @see ostatusConnect
  195. */
  196. public static function connectProfile(string $url, User $target): string
  197. {
  198. $target = common_local_url('userbyid', ['id' => $target->getID()]);
  199. // @fixme hack hack! We should look up the remote sub URL from XRDS
  200. $suburl = preg_replace('!^(.*)/(.*?)$!', '$1/main/ostatussub', $url);
  201. $suburl .= '?profile=' . urlencode($target);
  202. common_log(LOG_INFO, "Retrieving url $suburl for remote subscriber $url");
  203. return $suburl;
  204. }
  205. }