. * * @category Plugin * @package GNUsocial * @author Diogo Cordeiro * @author Daniel Supernault * @copyright 2018 Free Software Foundation http://fsf.org * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link https://www.gnu.org/software/social/ */ if (!defined('GNUSOCIAL')) { exit(1); } /** * Remote Follow * * @category Plugin * @package GNUsocial * @author Diogo Cordeiro * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://www.gnu.org/software/social/ */ class apRemoteFollowAction extends Action { public $nickname; public $local_profile; public $remote_identifier; public $err; /** * Prepare to handle the Remote Follow request. * * @author Diogo Cordeiro * @param array $args * @return boolean */ protected function prepare(array $args=array()) { parent::prepare($args); if (common_logged_in()) { // TRANS: Client error. $this->clientError(_m('You can use the local subscription!')); } // Local user the remote wants to subscribe to $this->nickname = $this->trimmed('nickname'); $this->local_profile = User::getByNickname($this->nickname)->getProfile(); // Webfinger or profile URL of the remote user $this->remote_identifier = $this->trimmed('remote_identifier'); return true; } /** * Handle the Remote Follow Request. * * @author Diogo Cordeiro */ protected function handle() { parent::handle(); if ($_SERVER['REQUEST_METHOD'] == 'POST') { /* Use a session token for CSRF protection. */ $token = $this->trimmed('token'); if (!$token || $token != common_session_token()) { // TRANS: Client error displayed when the session token does not match or is not given. $this->showForm(_m('There was a problem with your session token. '. 'Try again, please.')); return; } $this->activitypub_connect(); } else { $this->showForm(); } } /** * Form. * * @author GNU Social * @param string|null $err */ public function showForm($err = null) { $this->err = $err; if ($this->boolean('ajax')) { $this->startHTML('text/xml;charset=utf-8'); $this->elementStart('head'); // TRANS: Form title. $this->element('title', null, _m('TITLE', 'Subscribe to user')); $this->elementEnd('head'); $this->elementStart('body'); $this->showContent(); $this->elementEnd('body'); $this->endHTML(); } else { $this->showPage(); } } /** * Page content. * * @author GNU Social */ public function showContent() { // TRANS: Form legend. %s is a nickname. $header = sprintf(_m('Subscribe to %s'), $this->nickname); // TRANS: Button text to subscribe to a profile. $submit = _m('BUTTON', 'Subscribe'); $this->elementStart( 'form', ['id' => 'form_activitypub_connect', 'method' => 'post', 'class' => 'form_settings', 'action' => common_local_url( 'apRemoteFollow', ['nickname' => $this->nickname] ) ] ); $this->elementStart('fieldset'); $this->element('legend', null, $header); $this->hidden('token', common_session_token()); $this->elementStart('ul', 'form_data'); $this->elementStart('li', array('id' => 'activitypub_nickname')); // TRANS: Field label. $this->input( 'nickname', _m('User nickname'), $this->nickname, // TRANS: Field title. _m('Nickname of the user you want to follow.') ); $this->elementEnd('li'); $this->elementStart('li', array('id' => 'activitypub_profile')); // TRANS: Field label. $this->input( 'remote_identifier', _m('Profile Account'), $this->remote_identifier, // TRANS: Tooltip for field label "Profile Account". _m('Your account ID (e.g. user@example.net).') ); $this->elementEnd('li'); $this->elementEnd('ul'); $this->submit('submit', $submit); $this->elementEnd('fieldset'); $this->elementEnd('form'); } /** * Start connecting the two instances (will be finished with the authorization) * * @author Diogo Cordeiro * @return void */ public function activitypub_connect() { $remote_profile = null; try { // Try with ActivityPub system $remote_profile = Activitypub_profile::get_from_uri($this->remote_identifier); } catch (Exception $e) { // Fallback to compatibility WebFinger system $validate = new Validate(); $opts = array('allowed_schemes' => array('http', 'https', 'acct')); if ($validate->uri($this->remote_identifier, $opts)) { $bits = parse_url($this->remote_identifier); if ($bits['scheme'] == 'acct') { $remote_profile = $this->connect_webfinger($bits['path']); } } elseif (strpos($this->remote_identifier, '@') !== false) { $remote_profile = $this->connect_webfinger($this->remote_identifier); } } if (!empty($remote_profile)) { $url = ActivityPubPlugin::stripUrlPath($remote_profile->get_uri())."activitypub/authorize_follow?acct=".$this->local_profile->getUri(); common_log(LOG_INFO, "Sending remote subscriber $this->remote_identifier to $url"); common_redirect($url, 303); return; } // TRANS: Client error. $this->clientError(_m('Must provide a remote profile.')); } /** * This function is used by activitypub_connect () and * is a step of the process * * @author Diogo Cordeiro * @param type $acct * @return Profile Profile resulting of WebFinger connection */ private function connect_webfinger($acct) { $link = ActivityPubPlugin::pull_remote_profile($acct); if (!is_null($link)) { return $link; } // TRANS: Client error. $this->clientError(_m('Could not confirm remote profile address.')); } /** * Page title * * @return string Page title */ public function title() { // TRANS: Page title. return _m('ActivityPub Connect'); } }