/** * Retrieve Salmon keypair first by checking local database, but * if it's not found, attempt discovery if it has been requested. * * @param Profile $profile The profile we're looking up keys for. * @param boolean $discovery Network discovery if no local cache? */ public function getKeyPair(Profile $profile, $discovery = false) { if (!$profile->isLocal()) { common_debug('Getting magic-public-key for non-local profile id==' . $profile->getID()); } $magicsig = Magicsig::getKV('user_id', $profile->getID()); if ($discovery && !$magicsig instanceof Magicsig) { if (!$profile->isLocal()) { common_debug('magic-public-key not found, will do discovery for profile id==' . $profile->getID()); } // Throws exception on failure, but does not try to _load_ the keypair string. $keypair = $this->discoverKeyPair($profile); $magicsig = new Magicsig(); $magicsig->user_id = $profile->getID(); $magicsig->importKeys($keypair); // save the public key for this profile in our database. // TODO: If the profile generates a new key remotely, we must be able to replace // this (of course after callback-verification). $magicsig->insert(); } elseif (!$magicsig instanceof Magicsig) { // No discovery request, so we'll give up. throw new ServerException(sprintf('No public key found for profile (id==%d)', $profile->id)); } assert($magicsig->publicKey instanceof Crypt_RSA); return $magicsig; }
/** * Build common remote-profile options structure. * Currently only adds output for remote profiles, nothing for local users. * * @param HTMLOutputter $out * @param Profile $profile */ protected function showProfileOptions(HTMLOutputter $out, Profile $profile) { if (!$profile->isGroup() && !$profile->isLocal()) { $target = common_local_url('userbyid', array('id' => $profile->getID())); // TRANS: Label for access to remote profile options. $label = _m('Remote profile options...'); $out->elementStart('div', 'remote-profile-options'); $out->element('a', array('href' => $target), $label); $out->elementEnd('div'); } }
function onEndUnsubscribe(Profile $profile, Profile $other) { // Only do this if config is enabled if (!$this->StopFollowUser) { return true; } if (!$profile->isLocal()) { return true; } // TRANS: Text for "stopped following" item in activity plugin. // TRANS: %1$s is a profile URL, %2$s is a profile name, // TRANS: %3$s is a profile URL, %4$s is a profile name. $rendered = sprintf(_m('<a href="%1$s">%2$s</a> stopped following <a href="%3$s">%4$s</a>.'), $profile->getUrl(), $profile->getBestName(), $other->getUrl(), $other->getBestName()); // TRANS: Text for "stopped following" item in activity plugin. // TRANS: %1$s is a profile name, %2$s is a profile URL, // TRANS: %3$s is a profile name, %4$s is a profile URL. $content = sprintf(_m('%1$s (%2$s) stopped following %3$s (%4$s).'), $profile->getBestName(), $profile->getUrl(), $other->getBestName(), $other->getUrl()); $uri = TagURI::mint('stop-following:%d:%d:%s', $profile->id, $other->id, common_date_iso8601(common_sql_now())); $notice = Notice::saveNew($profile->id, $content, ActivityPlugin::SOURCE, array('rendered' => $rendered, 'urls' => array(), 'replies' => array($other->getUri()), 'uri' => $uri, 'verb' => ActivityVerb::UNFOLLOW, 'object_type' => ActivityObject::PERSON)); return true; }
/** * Saves an attention for a profile (user or group) which means * it shows up in their home feed and such. */ function saveAttention(Profile $target, $reason = null) { if ($target->isGroup()) { // FIXME: Make sure we check (for both local and remote) users are in the groups they send to! // legacy notification method, will still be in use for quite a while I think $this->addToGroupInbox($target->getGroup()); } else { if ($target->hasBlocked($this->getProfile())) { common_log(LOG_INFO, "Not saving reply to profile {$target->id} ({$uri}) from sender {$sender->id} because of a block."); return false; } } if ($target->isLocal()) { // legacy notification method, will still be in use for quite a while I think $this->saveReply($target->getID()); } $att = Attention::saveNew($this, $target, $reason); self::blow('reply:stream:%d', $target->getID()); return true; }
public function onEndProfilePageActionsElements(HTMLOutputter $out, Profile $profile) { $scoped = Profile::current(); if (!$scoped instanceof Profile) { return true; } if ($profile->isLocal() && $scoped->mutuallySubscribed($profile)) { $out->elementStart('li', 'entity_send-a-message'); $out->element('a', array('href' => common_local_url('newmessage', array('to' => $profile->id)), 'title' => _('Send a direct message to this user.')), _m('BUTTON', 'Message')); $out->elementEnd('li'); } return true; }
protected function addWebFingerPersonLinks(XML_XRD $xrd, Profile $target) { $xrd->links[] = new XML_XRD_Element_Link(Discovery::UPDATESFROM, common_local_url('ApiTimelineUser', array('id' => $target->id, 'format' => 'atom')), 'application/atom+xml'); // Get this profile's keypair $magicsig = Magicsig::getKV('user_id', $target->id); if (!$magicsig instanceof Magicsig && $target->isLocal()) { $magicsig = Magicsig::generate($target->getUser()); } if (!$magicsig instanceof Magicsig) { return false; // value doesn't mean anything, just figured I'd indicate this function didn't do anything } if (Event::handle('StartAttachPubkeyToUserXRD', array($magicsig, $xrd, $target))) { $xrd->links[] = new XML_XRD_Element_Link(Magicsig::PUBLICKEYREL, 'data:application/magic-public-key,' . $magicsig->toString()); // The following event handles plugins like Diaspora which add their own version of the Magicsig pubkey Event::handle('EndAttachPubkeyToUserXRD', array($magicsig, $xrd, $target)); } }
static function saveActivity(Activity $act, Profile $actor, array $options = array()) { // First check if we're going to let this Activity through from the specific actor if (!$actor->hasRight(Right::NEWNOTICE)) { common_log(LOG_WARNING, "Attempted post from user disallowed to post: " . $actor->getNickname()); // TRANS: Client exception thrown when a user tries to post while being banned. throw new ClientException(_m('You are banned from posting notices on this site.'), 403); } if (common_config('throttle', 'enabled') && !self::checkEditThrottle($actor->id)) { common_log(LOG_WARNING, 'Excessive posting by profile #' . $actor->id . '; throttled.'); // TRANS: Client exception thrown when a user tries to post too many notices in a given time frame. throw new ClientException(_m('Too many notices too fast; take a breather ' . 'and post again in a few minutes.')); } // Get ActivityObject properties if (!empty($act->id)) { // implied object $options['uri'] = $act->id; $options['url'] = $act->link; } else { $actobj = count($act->objects) == 1 ? $act->objects[0] : null; if (!is_null($actobj) && !empty($actobj->id)) { $options['uri'] = $actobj->id; if ($actobj->link) { $options['url'] = $actobj->link; } elseif (preg_match('!^https?://!', $actobj->id)) { $options['url'] = $actobj->id; } } } $defaults = array('groups' => array(), 'is_local' => self::LOCAL_PUBLIC, 'mentions' => array(), 'reply_to' => null, 'repeat_of' => null, 'scope' => null, 'source' => 'unknown', 'tags' => array(), 'uri' => null, 'url' => null, 'urls' => array(), 'distribute' => true); // options will have default values when nothing has been supplied $options = array_merge($defaults, $options); foreach (array_keys($defaults) as $key) { // Only convert the keynames we specify ourselves from 'defaults' array into variables ${$key} = $options[$key]; } extract($options, EXTR_SKIP); $stored = new Notice(); if (!empty($uri)) { $stored->uri = $uri; if ($stored->find()) { common_debug('cannot create duplicate Notice URI: ' . $stored->uri); throw new Exception('Notice URI already exists'); } } $stored->profile_id = $actor->id; $stored->source = $source; $stored->uri = $uri; $stored->url = $url; $stored->verb = $act->verb; // Use the local user's shortening preferences, if applicable. $stored->rendered = $actor->isLocal() ? $actor->shortenLinks($act->content) : $act->content; $stored->content = common_strip_html($stored->rendered); $autosource = common_config('public', 'autosource'); // Sandboxed are non-false, but not 1, either if (!$actor->hasRight(Right::PUBLICNOTICE) || $source && $autosource && in_array($source, $autosource)) { $stored->is_local = Notice::LOCAL_NONPUBLIC; } // Maybe a missing act-time should be fatal if the actor is not local? if (!empty($act->time)) { $stored->created = common_sql_date($act->time); } else { $stored->created = common_sql_now(); } $reply = null; if ($act->context instanceof ActivityContext && !empty($act->context->replyToID)) { $reply = self::getKV('uri', $act->context->replyToID); } if (!$reply instanceof Notice && $act->target instanceof ActivityObject) { $reply = self::getKV('uri', $act->target->id); } if ($reply instanceof Notice) { if (!$reply->inScope($actor)) { // TRANS: Client error displayed when trying to reply to a notice a the target has no access to. // TRANS: %1$s is a user nickname, %2$d is a notice ID (number). throw new ClientException(sprintf(_m('%1$s has no right to reply to notice %2$d.'), $actor->getNickname(), $reply->id), 403); } $stored->reply_to = $reply->id; $stored->conversation = $reply->conversation; // If the original is private to a group, and notice has no group specified, // make it to the same group(s) if (empty($groups) && $reply->scope & Notice::GROUP_SCOPE) { $groups = array(); $replyGroups = $reply->getGroups(); foreach ($replyGroups as $group) { if ($actor->isMember($group)) { $groups[] = $group->id; } } } if (is_null($scope)) { $scope = $reply->scope; } } if ($act->context instanceof ActivityContext) { $location = $act->context->location; if ($location) { $stored->lat = $location->lat; $stored->lon = $location->lon; if ($location->location_id) { $stored->location_ns = $location->location_ns; $stored->location_id = $location->location_id; } } } else { $act->context = new ActivityContext(); } $stored->scope = self::figureOutScope($actor, $groups, $scope); foreach ($act->categories as $cat) { if ($cat->term) { $term = common_canonical_tag($cat->term); if (!empty($term)) { $tags[] = $term; } } } foreach ($act->enclosures as $href) { // @todo FIXME: Save these locally or....? $urls[] = $href; } if (Event::handle('StartNoticeSave', array(&$stored))) { // XXX: some of these functions write to the DB try { $stored->insert(); // throws exception on error $orig = clone $stored; // for updating later in this try clause $object = null; Event::handle('StoreActivityObject', array($act, $stored, $options, &$object)); if (empty($object)) { throw new ServerException('Unsuccessful call to StoreActivityObject ' . $stored->uri . ': ' . $act->asString()); } // If it's not part of a conversation, it's // the beginning of a new conversation. if (empty($stored->conversation)) { // $act->context->conversation will be null if it was not provided $conv = Conversation::create($stored, $act->context->conversation); $stored->conversation = $conv->id; } $stored->update($orig); } catch (Exception $e) { if (empty($stored->id)) { common_debug('Failed to save stored object entry in database (' . $e->getMessage() . ')'); } else { common_debug('Failed to store activity object in database (' . $e->getMessage() . '), deleting notice id ' . $stored->id); $stored->delete(); } throw $e; } } if (!$stored instanceof Notice) { throw new ServerException('StartNoticeSave did not give back a Notice'); } // Save per-notice metadata... $mentions = array(); $groups = array(); // This event lets plugins filter out non-local recipients (attentions we don't care about) // Used primarily for OStatus (and if we don't federate, all attentions would be local anyway) Event::handle('GetLocalAttentions', array($actor, $act->context->attention, &$mentions, &$groups)); if (!empty($mentions)) { $stored->saveKnownReplies($mentions); } else { $stored->saveReplies(); } if (!empty($tags)) { $stored->saveKnownTags($tags); } else { $stored->saveTags(); } // Note: groups may save tags, so must be run after tags are saved // to avoid errors on duplicates. // Note: groups should always be set. $stored->saveKnownGroups($groups); if (!empty($urls)) { $stored->saveKnownUrls($urls); } else { $stored->saveUrls(); } if ($distribute) { // Prepare inbox delivery, may be queued to background. $stored->distribute(); } return $stored; }