Merge branch 'next-minor' into develop

This commit is contained in:
Benjamin Neff 2018-07-11 01:35:50 +02:00
commit dcbdb69e22
No known key found for this signature in database
GPG key ID: 971464C3F1A90194
15 changed files with 249 additions and 4 deletions

View file

@ -19,6 +19,7 @@
* Add compatibility with macOS to `script/configure_bundler` [#7830](https://github.com/diaspora/diaspora/pull/7830)
## Features
* Add `web+diaspora://` link handler [#7826](https://github.com/diaspora/diaspora/pull/7826)
# 0.7.6.0

View file

@ -48,3 +48,4 @@
//= require jquery.are-you-sure
//= require cropperjs/dist/cropper.js
//= require pica
//= require protocol-handler

View file

@ -0,0 +1,17 @@
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
function registerDiasporaLinksProtocol() {
var protocol = location.protocol;
var slashes = protocol.concat("//");
var host = slashes.concat(window.location.hostname);
if (location.port) {
host = host.concat(":" + location.port);
}
window.navigator.registerProtocolHandler("web+diaspora", host.concat("/link?q=%s"), document.title);
}
if (typeof (window.navigator.registerProtocolHandler) === "function") {
registerDiasporaLinksProtocol();
}

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
class LinksController < ApplicationController
def resolve
entity = DiasporaLinkService.new(query).find_or_fetch_entity
raise ActiveRecord::RecordNotFound if entity.nil?
redirect_to url_for(entity)
end
private
def query
@query ||= params.fetch(:q)
end
end

View file

@ -22,8 +22,7 @@ class Reference < ApplicationRecord
private
def add_reference(author, type, guid)
class_name = DiasporaFederation::Entity.entity_class(type).to_s.rpartition("::").last
entity = Diaspora::Federation::Mappings.model_class_for(class_name).find_by(guid: guid)
entity = Diaspora::EntityFinder.new(type, guid).find
references.find_or_create_by(target: entity) if entity&.diaspora_handle == author
rescue => e # rubocop:disable Lint/RescueWithoutErrorClass
logger.warn "ignoring invalid diaspora-url: diaspora://#{author}/#{type}/#{guid}: #{e.class}: #{e.message}"

View file

@ -37,6 +37,10 @@ class StatusMessage < Post
owned_or_visible_by_user(person.owner).joins(:mentions).where(mentions: {person_id: person.id})
}
def self.model_name
Post.model_name
end
def self.guids_for_author(person)
Post.connection.select_values(Post.where(:author_id => person.id).select('posts.guid').to_sql)
end

View file

@ -0,0 +1,45 @@
# frozen_string_literal: true
# Encapsulates logic of processing diaspora:// links
class DiasporaLinkService
attr_reader :type, :author, :guid
def initialize(link)
@link = link.dup
parse
end
def find_or_fetch_entity
entity_finder.find || fetch_entity
end
private
attr_accessor :link
def fetch_entity
DiasporaFederation::Federation::Fetcher.fetch_public(author, type, guid)
entity_finder.find
rescue DiasporaFederation::Federation::Fetcher::NotFetchable
nil
end
def entity_finder
@entity_finder ||= Diaspora::EntityFinder.new(type, guid)
end
def normalize
link.gsub!(%r{^web\+diaspora://}, "diaspora://") ||
link.gsub!(%r{^//}, "diaspora://") ||
%r{^diaspora://}.match(link) ||
self.link = "diaspora://#{link}"
end
def parse
normalize
match = DiasporaFederation::Federation::DiasporaUrlParser::DIASPORA_URL_REGEX.match(link)
@author = match[1]
@type = match[2]
@guid = match[3]
end
end

View file

@ -1,6 +1,6 @@
.row.publisher#publisher{class: ((aspect == :profile || publisher_open?) ? "mention_popup" : "closed")}
.content_creation
= form_for(StatusMessage.new) do |status|
= form_for StatusMessage.new, url: status_messages_path, as: :status_message do |status|
= status.error_messages
%params
.publisher-textarea-wrapper#publisher-textarea-wrapper

View file

@ -2,7 +2,8 @@
-# licensed under the Affero General Public License version 3 or later. See
-# the COPYRIGHT file.
= form_for StatusMessage.new, html: {class: "control-group", data: {ajax: false}} do |status|
= form_for StatusMessage.new, url: status_messages_path, as: :status_message,
html: {class: "control-group", data: {ajax: false}} do |status|
.form-group
= status.hidden_field :provider_display_name, value: 'mobile'
= status.text_area :text, placeholder: t('shared.publisher.whats_on_your_mind'), rows: 4, autofocus: "autofocus", class: "form-control"

View file

@ -74,6 +74,8 @@ Rails.application.routes.draw do
#Search
get 'search' => "search#search"
get "link" => "links#resolve"
resources :conversations, except: %i(edit update destroy) do
resources :messages, only: %i(create)
delete 'visibility' => 'conversation_visibilities#destroy'

View file

@ -0,0 +1,15 @@
@javascript
Feature: (web+)diaspora:// links resolve
In order to open diaspora posts on my pod from external websites
As a user
I want external links to be resolved to local pod paths
Background:
Given following user exists:
| username | email |
| Alice | alice@alice.alice |
And "alice@alice.alice" has a public post with text "This is a post accessed by an external link"
Scenario: Resolving web+diaspora:// link
When I open an external link to the first post of "alice@alice.alice"
Then I should see "This is a post accessed by an external link"

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
When /^I open an external link to the first post of "([^"]*)"$/ do |email|
user = User.find_by(email: email)
post = user.posts.first
visit(link_path(q: "web+diaspora://#{user.diaspora_handle}/post/#{post.guid}"))
end

View file

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Diaspora
class EntityFinder
def initialize(type, guid)
@type = type
@guid = guid
end
def class_name
@class_name ||= DiasporaFederation::Entity.entity_class(type).to_s.rpartition("::").last
end
def find
Diaspora::Federation::Mappings.model_class_for(class_name).find_by(guid: guid)
end
private
attr_reader :type, :guid
end
end

View file

@ -0,0 +1,71 @@
# frozen_string_literal: true
describe LinksController, type: :controller do
describe "#resolve" do
context "with post" do
let(:post) { FactoryGirl.create(:status_message) }
let(:link_text) { "#{post.author.diaspora_handle}/post/#{post.guid}" }
subject { get :resolve, params: {q: link_query} }
shared_examples "redirects to the post" do
it "redirects to the post" do
expect(subject).to redirect_to(post_url(post))
end
end
context "with stripped link text" do
let(:link_query) { link_text }
include_examples "redirects to the post"
end
context "with link text starting with //" do
let(:link_query) { "//#{link_text}" }
include_examples "redirects to the post"
end
context "with link text starting with diaspora://" do
let(:link_query) { "diaspora://#{link_text}" }
include_examples "redirects to the post"
end
context "with link text starting with web+diaspora://" do
let(:link_query) { "web+diaspora://#{link_text}" }
include_examples "redirects to the post"
end
context "when post is non-fetchable" do
let(:diaspora_id) { FactoryGirl.create(:person).diaspora_handle }
let(:guid) { "1234567890abcdef" }
let(:link_query) { "web+diaspora://#{diaspora_id}/post/#{guid}" }
before do
expect(DiasporaFederation::Federation::Fetcher)
.to receive(:fetch_public)
.with(diaspora_id, "post", guid)
.and_raise(DiasporaFederation::Federation::Fetcher::NotFetchable)
end
it "responds 404" do
expect { subject }.to raise_error(ActiveRecord::RecordNotFound)
end
end
context "when user is non-fetchable" do
let(:diaspora_id) { "unknown@pod.tld" }
let(:guid) { "1234567890abcdef" }
let(:link_query) { "web+diaspora://#{diaspora_id}/post/#{guid}" }
before do
expect(Person)
.to receive(:find_or_fetch_by_identifier)
.with(diaspora_id)
.and_return(nil)
end
it "responds 404" do
expect { subject }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
end
end

View file

@ -0,0 +1,44 @@
# frozen_string_literal: true
describe DiasporaLinkService do
let(:service) { described_class.new(link) }
describe "#find_or_fetch_entity" do
context "when entity is known" do
let(:post) { FactoryGirl.create(:status_message) }
let(:link) { "diaspora://#{post.author.diaspora_handle}/post/#{post.guid}" }
it "returns the entity" do
expect(service.find_or_fetch_entity).to eq(post)
end
end
context "when entity is unknown" do
let(:remote_person) { FactoryGirl.create(:person) }
let(:guid) { "1234567890abcdef" }
let(:link) { "diaspora://#{remote_person.diaspora_handle}/post/#{guid}" }
it "fetches entity" do
expect(DiasporaFederation::Federation::Fetcher)
.to receive(:fetch_public)
.with(remote_person.diaspora_handle, "post", guid) {
FactoryGirl.create(:status_message, author: remote_person, guid: guid)
}
entity = service.find_or_fetch_entity
expect(entity).to be_a(StatusMessage)
expect(entity.guid).to eq(guid)
expect(entity.author).to eq(remote_person)
end
it "returns nil when entity is non fetchable" do
expect(DiasporaFederation::Federation::Fetcher)
.to receive(:fetch_public)
.with(remote_person.diaspora_handle, "post", guid)
.and_raise(DiasporaFederation::Federation::Fetcher::NotFetchable)
expect(service.find_or_fetch_entity).to be_nil
end
end
end
end