From 09904b54d0a02addc387f872caec2ce10e9a9924 Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Sat, 11 Jul 2015 17:23:19 +0200 Subject: [PATCH 01/33] rename WebFinger module to Discovery --- README.md | 2 +- .../webfinger_controller.rb | 4 ++-- lib/diaspora_federation.rb | 2 +- lib/diaspora_federation/discovery.rb | 13 ++++++++++++ .../{web_finger => discovery}/exceptions.rb | 2 +- .../{web_finger => discovery}/h_card.rb | 2 +- .../{web_finger => discovery}/host_meta.rb | 2 +- .../{web_finger => discovery}/web_finger.rb | 2 +- .../{web_finger => discovery}/xrd_document.rb | 2 +- lib/diaspora_federation/web_finger.rb | 13 ------------ .../webfinger_controller_spec.rb | 6 +++--- .../{web_finger => discovery}/h_card_spec.rb | 20 +++++++++---------- .../host_meta_spec.rb | 20 +++++++++---------- .../web_finger_spec.rb | 18 ++++++++--------- .../xrd_document_spec.rb | 10 +++++----- .../initializers/diaspora_federation.rb | 6 +++--- 16 files changed, 62 insertions(+), 62 deletions(-) create mode 100644 lib/diaspora_federation/discovery.rb rename lib/diaspora_federation/{web_finger => discovery}/exceptions.rb (96%) rename lib/diaspora_federation/{web_finger => discovery}/h_card.rb (99%) rename lib/diaspora_federation/{web_finger => discovery}/host_meta.rb (99%) rename lib/diaspora_federation/{web_finger => discovery}/web_finger.rb (99%) rename lib/diaspora_federation/{web_finger => discovery}/xrd_document.rb (99%) delete mode 100644 lib/diaspora_federation/web_finger.rb rename spec/lib/diaspora_federation/{web_finger => discovery}/h_card_spec.rb (91%) rename spec/lib/diaspora_federation/{web_finger => discovery}/host_meta_spec.rb (71%) rename spec/lib/diaspora_federation/{web_finger => discovery}/web_finger_spec.rb (87%) rename spec/lib/diaspora_federation/{web_finger => discovery}/xrd_document_spec.rb (87%) diff --git a/README.md b/README.md index a7818ed..4faf7e0 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ DiasporaFederation.configure do |config| on :person_webfinger_fetch do |handle| person = Person.find_local_by_diaspora_handle(handle) if person - DiasporaFederation::WebFinger::WebFinger.new( + DiasporaFederation::Discovery::WebFinger.new( # ... ) end diff --git a/app/controllers/diaspora_federation/webfinger_controller.rb b/app/controllers/diaspora_federation/webfinger_controller.rb index 3343e1c..22bc63a 100644 --- a/app/controllers/diaspora_federation/webfinger_controller.rb +++ b/app/controllers/diaspora_federation/webfinger_controller.rb @@ -48,11 +48,11 @@ module DiasporaFederation # creates the host-meta xml with the configured server_uri and caches it # @return [String] XML string def self.host_meta_xml - @host_meta_xml ||= WebFinger::HostMeta.from_base_url(DiasporaFederation.server_uri.to_s).to_xml + @host_meta_xml ||= Discovery::HostMeta.from_base_url(DiasporaFederation.server_uri.to_s).to_xml end def find_person_webfinger(query) - DiasporaFederation.callbacks.trigger(:person_webfinger_fetch, query.strip.downcase.gsub("acct:", "")) + DiasporaFederation.callbacks.trigger(:person_webfinger_fetch, query.strip.downcase.sub("acct:", "")) end end end diff --git a/lib/diaspora_federation.rb b/lib/diaspora_federation.rb index 1580b65..2dbf8c8 100644 --- a/lib/diaspora_federation.rb +++ b/lib/diaspora_federation.rb @@ -4,7 +4,7 @@ require "diaspora_federation/callbacks" require "diaspora_federation/properties_dsl" require "diaspora_federation/entity" -require "diaspora_federation/web_finger" +require "diaspora_federation/discovery" # diaspora* federation library module DiasporaFederation diff --git a/lib/diaspora_federation/discovery.rb b/lib/diaspora_federation/discovery.rb new file mode 100644 index 0000000..d917b32 --- /dev/null +++ b/lib/diaspora_federation/discovery.rb @@ -0,0 +1,13 @@ +module DiasporaFederation + # This module provides the namespace for the various classes implementing + # WebFinger and other protocols used for metadata discovery on remote servers + # in the Diaspora* network. + module Discovery + end +end + +require "diaspora_federation/discovery/exceptions" +require "diaspora_federation/discovery/xrd_document" +require "diaspora_federation/discovery/host_meta" +require "diaspora_federation/discovery/web_finger" +require "diaspora_federation/discovery/h_card" diff --git a/lib/diaspora_federation/web_finger/exceptions.rb b/lib/diaspora_federation/discovery/exceptions.rb similarity index 96% rename from lib/diaspora_federation/web_finger/exceptions.rb rename to lib/diaspora_federation/discovery/exceptions.rb index 5883dc7..cefced1 100644 --- a/lib/diaspora_federation/web_finger/exceptions.rb +++ b/lib/diaspora_federation/discovery/exceptions.rb @@ -1,5 +1,5 @@ module DiasporaFederation - module WebFinger + module Discovery # Raised, if the XML structure is invalid class InvalidDocument < RuntimeError end diff --git a/lib/diaspora_federation/web_finger/h_card.rb b/lib/diaspora_federation/discovery/h_card.rb similarity index 99% rename from lib/diaspora_federation/web_finger/h_card.rb rename to lib/diaspora_federation/discovery/h_card.rb index 76025e9..e3f149b 100644 --- a/lib/diaspora_federation/web_finger/h_card.rb +++ b/lib/diaspora_federation/discovery/h_card.rb @@ -1,5 +1,5 @@ module DiasporaFederation - module WebFinger + module Discovery # This class provides the means of generating an parsing account data to and # from the hCard format. # hCard is based on +RFC 2426+ (vCard) which got superseded by +RFC 6350+. diff --git a/lib/diaspora_federation/web_finger/host_meta.rb b/lib/diaspora_federation/discovery/host_meta.rb similarity index 99% rename from lib/diaspora_federation/web_finger/host_meta.rb rename to lib/diaspora_federation/discovery/host_meta.rb index 444c16f..fce8077 100644 --- a/lib/diaspora_federation/web_finger/host_meta.rb +++ b/lib/diaspora_federation/discovery/host_meta.rb @@ -1,6 +1,6 @@ module DiasporaFederation - module WebFinger + module Discovery # Generates and parses Host Meta documents. # # This is a minimal implementation of the standard, only to the degree of what diff --git a/lib/diaspora_federation/web_finger/web_finger.rb b/lib/diaspora_federation/discovery/web_finger.rb similarity index 99% rename from lib/diaspora_federation/web_finger/web_finger.rb rename to lib/diaspora_federation/discovery/web_finger.rb index a0ddc7a..ddeabd1 100644 --- a/lib/diaspora_federation/web_finger/web_finger.rb +++ b/lib/diaspora_federation/discovery/web_finger.rb @@ -1,5 +1,5 @@ module DiasporaFederation - module WebFinger + module Discovery # The WebFinger document used for Diaspora* user discovery is based on an older # draft of the specification you can find in the wiki of the "webfinger" project # on {http://code.google.com/p/webfinger/wiki/WebFingerProtocol Google Code} diff --git a/lib/diaspora_federation/web_finger/xrd_document.rb b/lib/diaspora_federation/discovery/xrd_document.rb similarity index 99% rename from lib/diaspora_federation/web_finger/xrd_document.rb rename to lib/diaspora_federation/discovery/xrd_document.rb index d35fbe2..c03f077 100644 --- a/lib/diaspora_federation/web_finger/xrd_document.rb +++ b/lib/diaspora_federation/discovery/xrd_document.rb @@ -1,5 +1,5 @@ module DiasporaFederation - module WebFinger + module Discovery # This class implements basic handling of XRD documents as far as it is # necessary in the context of the protocols used with Diaspora* federation. # diff --git a/lib/diaspora_federation/web_finger.rb b/lib/diaspora_federation/web_finger.rb deleted file mode 100644 index f0d6222..0000000 --- a/lib/diaspora_federation/web_finger.rb +++ /dev/null @@ -1,13 +0,0 @@ -module DiasporaFederation - # This module provides the namespace for the various classes implementing - # WebFinger and other protocols used for metadata discovery on remote servers - # in the Diaspora* network. - module WebFinger - end -end - -require "diaspora_federation/web_finger/exceptions" -require "diaspora_federation/web_finger/xrd_document" -require "diaspora_federation/web_finger/host_meta" -require "diaspora_federation/web_finger/web_finger" -require "diaspora_federation/web_finger/h_card" diff --git a/spec/controllers/diaspora_federation/webfinger_controller_spec.rb b/spec/controllers/diaspora_federation/webfinger_controller_spec.rb index 62b42cb..3a7c87e 100644 --- a/spec/controllers/diaspora_federation/webfinger_controller_spec.rb +++ b/spec/controllers/diaspora_federation/webfinger_controller_spec.rb @@ -24,13 +24,13 @@ module DiasporaFederation expect(response.header["Content-Type"]).to include "application/xrd+xml" end - it "calls WebFinger::HostMeta.from_base_url with the base url" do - expect(WebFinger::HostMeta).to receive(:from_base_url).with("http://localhost:3000/").and_call_original + it "calls Discovery::HostMeta.from_base_url with the base url" do + expect(Discovery::HostMeta).to receive(:from_base_url).with("http://localhost:3000/").and_call_original get :host_meta end it "caches the xml" do - expect(WebFinger::HostMeta).to receive(:from_base_url).exactly(1).times.and_call_original + expect(Discovery::HostMeta).to receive(:from_base_url).exactly(1).times.and_call_original get :host_meta get :host_meta end diff --git a/spec/lib/diaspora_federation/web_finger/h_card_spec.rb b/spec/lib/diaspora_federation/discovery/h_card_spec.rb similarity index 91% rename from spec/lib/diaspora_federation/web_finger/h_card_spec.rb rename to spec/lib/diaspora_federation/discovery/h_card_spec.rb index c7e124a..3e763d2 100644 --- a/spec/lib/diaspora_federation/web_finger/h_card_spec.rb +++ b/spec/lib/diaspora_federation/discovery/h_card_spec.rb @@ -1,5 +1,5 @@ module DiasporaFederation - describe WebFinger::HCard do + describe Discovery::HCard do let(:person) { FactoryGirl.create(:person) } let(:photo_large_url) { "#{person.url}/upload/large.png" } let(:photo_medium_url) { "#{person.url}/upload/medium.png" } @@ -93,12 +93,12 @@ HTML } it "must not create blank instances" do - expect { WebFinger::HCard.new({}) }.to raise_error ArgumentError + expect { Discovery::HCard.new({}) }.to raise_error ArgumentError end context "generation" do it "creates an instance from a data hash" do - hcard = WebFinger::HCard.new( + hcard = Discovery::HCard.new( guid: person.guid, nickname: person.nickname, full_name: person.full_name, @@ -115,13 +115,13 @@ HTML end it "fails if nil was given" do - expect { WebFinger::HCard.new(nil) }.to raise_error ArgumentError, "expected a Hash" + expect { Discovery::HCard.new(nil) }.to raise_error ArgumentError, "expected a Hash" end end context "parsing" do it "reads its own output" do - hcard = WebFinger::HCard.from_html(html) + hcard = Discovery::HCard.from_html(html) expect(hcard.guid).to eq(person.guid) expect(hcard.nickname).to eq(person.nickname) expect(hcard.full_name).to eq(person.full_name) @@ -137,7 +137,7 @@ HTML end it "is frozen after parsing" do - hcard = WebFinger::HCard.from_html(html) + hcard = Discovery::HCard.from_html(html) expect(hcard).to be_frozen end @@ -147,7 +147,7 @@ HTML "class=\"searchable\"><" ) - hcard = WebFinger::HCard.from_html(changed_html) + hcard = Discovery::HCard.from_html(changed_html) expect(hcard.searchable).to eq(false) end @@ -218,7 +218,7 @@ HTML HTML - hcard = WebFinger::HCard.from_html(historic_html) + hcard = Discovery::HCard.from_html(historic_html) expect(hcard.url).to eq(person.url) expect(hcard.photo_large_url).to eq(photo_large_url) expect(hcard.photo_medium_url).to eq(photo_medium_url) @@ -235,11 +235,11 @@ HTML #{person.full_name} HTML - expect { WebFinger::HCard.from_html(invalid_html) }.to raise_error WebFinger::InvalidData + expect { Discovery::HCard.from_html(invalid_html) }.to raise_error Discovery::InvalidData end it "fails if the document is not HTML" do - expect { WebFinger::HCard.from_html("") }.to raise_error WebFinger::InvalidData + expect { Discovery::HCard.from_html("") }.to raise_error Discovery::InvalidData end end end diff --git a/spec/lib/diaspora_federation/web_finger/host_meta_spec.rb b/spec/lib/diaspora_federation/discovery/host_meta_spec.rb similarity index 71% rename from spec/lib/diaspora_federation/web_finger/host_meta_spec.rb rename to spec/lib/diaspora_federation/discovery/host_meta_spec.rb index 0bb9545..2f82ebf 100644 --- a/spec/lib/diaspora_federation/web_finger/host_meta_spec.rb +++ b/spec/lib/diaspora_federation/discovery/host_meta_spec.rb @@ -1,5 +1,5 @@ module DiasporaFederation - describe WebFinger::HostMeta do + describe Discovery::HostMeta do let(:base_url) { "https://pod.example.tld/" } let(:xml) { <<-XML @@ -11,28 +11,28 @@ XML } it "must not create blank instances" do - expect { WebFinger::HostMeta.new }.to raise_error NoMethodError + expect { Discovery::HostMeta.new }.to raise_error NoMethodError end context "generation" do it "creates a nice XML document" do - hm = WebFinger::HostMeta.from_base_url(base_url) + hm = Discovery::HostMeta.from_base_url(base_url) expect(hm.to_xml).to eq(xml) end it "appends a '/' if necessary" do - hm = WebFinger::HostMeta.from_base_url("https://pod.example.tld") + hm = Discovery::HostMeta.from_base_url("https://pod.example.tld") expect(hm.to_xml).to eq(xml) end it "fails if the base_url was omitted" do - expect { WebFinger::HostMeta.from_base_url("") }.to raise_error WebFinger::InvalidData + expect { Discovery::HostMeta.from_base_url("") }.to raise_error Discovery::InvalidData end end context "parsing" do it "parses its own output" do - hm = WebFinger::HostMeta.from_xml(xml) + hm = Discovery::HostMeta.from_xml(xml) expect(hm.webfinger_template_url).to eq("#{base_url}webfinger?q={uri}") end @@ -49,7 +49,7 @@ XML XML - hm = WebFinger::HostMeta.from_xml(historic_xml) + hm = Discovery::HostMeta.from_xml(historic_xml) expect(hm.webfinger_template_url).to eq("#{base_url}webfinger?q={uri}") end @@ -59,7 +59,7 @@ XML XML - expect { WebFinger::HostMeta.from_xml(invalid_xml) }.to raise_error WebFinger::InvalidData + expect { Discovery::HostMeta.from_xml(invalid_xml) }.to raise_error Discovery::InvalidData end it "fails if the document contains a malformed webfinger url" do @@ -69,11 +69,11 @@ XML XML - expect { WebFinger::HostMeta.from_xml(invalid_xml) }.to raise_error WebFinger::InvalidData + expect { Discovery::HostMeta.from_xml(invalid_xml) }.to raise_error Discovery::InvalidData end it "fails if the document is invalid" do - expect { WebFinger::HostMeta.from_xml("") }.to raise_error WebFinger::InvalidDocument + expect { Discovery::HostMeta.from_xml("") }.to raise_error Discovery::InvalidDocument end end end diff --git a/spec/lib/diaspora_federation/web_finger/web_finger_spec.rb b/spec/lib/diaspora_federation/discovery/web_finger_spec.rb similarity index 87% rename from spec/lib/diaspora_federation/web_finger/web_finger_spec.rb rename to spec/lib/diaspora_federation/discovery/web_finger_spec.rb index e5313b7..340e321 100644 --- a/spec/lib/diaspora_federation/web_finger/web_finger_spec.rb +++ b/spec/lib/diaspora_federation/discovery/web_finger_spec.rb @@ -1,5 +1,5 @@ module DiasporaFederation - describe WebFinger::WebFinger do + describe Discovery::WebFinger do let(:person) { FactoryGirl.create(:person) } let(:acct) { "acct:#{person.diaspora_handle}" } let(:public_key_base64) { Base64.strict_encode64(person.serialized_public_key) } @@ -22,12 +22,12 @@ XML } it "must not create blank instances" do - expect { WebFinger::WebFinger.new({}) }.to raise_error ArgumentError + expect { Discovery::WebFinger.new({}) }.to raise_error ArgumentError end context "generation" do it "creates a nice XML document" do - wf = WebFinger::WebFinger.new( + wf = Discovery::WebFinger.new( acct_uri: "acct:#{person.diaspora_handle}", alias_url: person.alias_url, hcard_url: person.hcard_url, @@ -42,13 +42,13 @@ XML end it "fails if nil was given" do - expect { WebFinger::WebFinger.new(nil) }.to raise_error ArgumentError, "expected a Hash" + expect { Discovery::WebFinger.new(nil) }.to raise_error ArgumentError, "expected a Hash" end end context "parsing" do it "reads its own output" do - wf = WebFinger::WebFinger.from_xml(xml) + wf = Discovery::WebFinger.from_xml(xml) expect(wf.acct_uri).to eq(acct) expect(wf.alias_url).to eq(person.alias_url) expect(wf.hcard_url).to eq(person.hcard_url) @@ -62,7 +62,7 @@ XML end it "is frozen after parsing" do - wf = WebFinger::WebFinger.from_xml(xml) + wf = Discovery::WebFinger.from_xml(xml) expect(wf).to be_frozen end @@ -84,7 +84,7 @@ XML XML - wf = WebFinger::WebFinger.from_xml(historic_xml) + wf = Discovery::WebFinger.from_xml(historic_xml) expect(wf.acct_uri).to eq(acct) expect(wf.alias_url).to eq(person.alias_url) expect(wf.hcard_url).to eq(person.hcard_url) @@ -103,11 +103,11 @@ XML XML - expect { WebFinger::WebFinger.from_xml(invalid_xml) }.to raise_error WebFinger::InvalidData + expect { Discovery::WebFinger.from_xml(invalid_xml) }.to raise_error Discovery::InvalidData end it "fails if the document is not XML" do - expect { WebFinger::WebFinger.from_xml("") }.to raise_error WebFinger::InvalidDocument + expect { Discovery::WebFinger.from_xml("") }.to raise_error Discovery::InvalidDocument end end end diff --git a/spec/lib/diaspora_federation/web_finger/xrd_document_spec.rb b/spec/lib/diaspora_federation/discovery/xrd_document_spec.rb similarity index 87% rename from spec/lib/diaspora_federation/web_finger/xrd_document_spec.rb rename to spec/lib/diaspora_federation/discovery/xrd_document_spec.rb index 18d566d..42ba34f 100644 --- a/spec/lib/diaspora_federation/web_finger/xrd_document_spec.rb +++ b/spec/lib/diaspora_federation/discovery/xrd_document_spec.rb @@ -1,5 +1,5 @@ module DiasporaFederation - describe WebFinger::XrdDocument do + describe Discovery::XrdDocument do let(:xml) { < @@ -49,7 +49,7 @@ XML context "generation" do it "creates the xml document" do - doc = WebFinger::XrdDocument.new + doc = Discovery::XrdDocument.new doc.expires = data[:expires] doc.subject = data[:subject] @@ -71,16 +71,16 @@ XML context "parsing" do it "reads the xml document" do - doc = WebFinger::XrdDocument.xml_data(xml) + doc = Discovery::XrdDocument.xml_data(xml) expect(doc).to eq(data) end it "raises InvalidDocument if the xml is empty" do - expect { WebFinger::XrdDocument.xml_data("") }.to raise_error WebFinger::InvalidDocument + expect { Discovery::XrdDocument.xml_data("") }.to raise_error Discovery::InvalidDocument end it "raises InvalidDocument if the xml is no XRD document" do - expect { WebFinger::XrdDocument.xml_data("") }.to raise_error WebFinger::InvalidDocument + expect { Discovery::XrdDocument.xml_data("") }.to raise_error Discovery::InvalidDocument end end end diff --git a/test/dummy/config/initializers/diaspora_federation.rb b/test/dummy/config/initializers/diaspora_federation.rb index a41a7c5..1374fff 100644 --- a/test/dummy/config/initializers/diaspora_federation.rb +++ b/test/dummy/config/initializers/diaspora_federation.rb @@ -1,4 +1,4 @@ -require "diaspora_federation/web_finger" +require "diaspora_federation/discovery" # configure the federation engine DiasporaFederation.configure do |config| @@ -9,7 +9,7 @@ DiasporaFederation.configure do |config| on :person_webfinger_fetch do |handle| person = Person.find_by(diaspora_handle: handle) if person - DiasporaFederation::WebFinger::WebFinger.new( + DiasporaFederation::Discovery::WebFinger.new( acct_uri: "acct:#{person.diaspora_handle}", alias_url: person.alias_url, hcard_url: person.hcard_url, @@ -26,7 +26,7 @@ DiasporaFederation.configure do |config| on :person_hcard_fetch do |guid| person = Person.find_by(guid: guid) if person - DiasporaFederation::WebFinger::HCard.new( + DiasporaFederation::Discovery::HCard.new( guid: person.guid, nickname: person.nickname, full_name: person.full_name, From 0204b3d9ffac2c47f275a788b639c8f9197c585b Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Sun, 12 Jul 2015 14:59:05 +0200 Subject: [PATCH 02/33] add Fetcher for http requests --- Gemfile | 1 + Gemfile.lock | 20 +++++++ diaspora_federation.gemspec | 3 + lib/diaspora_federation.rb | 17 +++++- lib/diaspora_federation/fetcher.rb | 42 ++++++++++++++ spec/lib/diaspora_federation/fetcher_spec.rb | 56 +++++++++++++++++++ spec/lib/diaspora_federation_spec.rb | 23 +++++++- spec/spec_helper.rb | 1 + .../initializers/diaspora_federation.rb | 10 ++++ 9 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 lib/diaspora_federation/fetcher.rb create mode 100644 spec/lib/diaspora_federation/fetcher_spec.rb diff --git a/Gemfile b/Gemfile index 862f93f..c9d145e 100644 --- a/Gemfile +++ b/Gemfile @@ -42,6 +42,7 @@ group :test do gem "fixture_builder", "~> 0.4.1" gem "factory_girl_rails", "~> 4.5.0" gem "rspec-collection_matchers", "~> 1.1.2" + gem "webmock", "~> 1.21.0" end group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index ffd57ed..7eac604 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,7 +2,10 @@ PATH remote: . specs: diaspora_federation (0.0.3) + faraday (~> 0.9.0) + faraday_middleware (~> 0.9.0) nokogiri (~> 1.6, >= 1.6.6) + typhoeus (~> 0.7.0) diaspora_federation-rails (0.0.3) diaspora_federation (= 0.0.3) rails (~> 4.2) @@ -45,6 +48,7 @@ GEM minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + addressable (2.3.8) arel (6.0.0) ast (2.0.0) astrolabe (1.3.0) @@ -56,14 +60,22 @@ GEM simplecov (>= 0.7.1, < 1.0.0) coderay (1.1.0) columnize (0.9.0) + crack (0.4.2) + safe_yaml (~> 1.0.0) diff-lcs (1.2.5) docile (1.1.5) erubis (2.7.0) + ethon (0.7.4) + ffi (>= 1.3.0) factory_girl (4.5.0) activesupport (>= 3.0.0) factory_girl_rails (4.5.0) factory_girl (~> 4.5.0) railties (>= 3.0.0) + faraday (0.9.1) + multipart-post (>= 1.2, < 3) + faraday_middleware (0.9.2) + faraday (>= 0.7.4, < 0.10) ffi (1.9.10) fixture_builder (0.4.1) activerecord (>= 2) @@ -111,6 +123,7 @@ GEM mini_portile (0.6.2) minitest (5.7.0) multi_json (1.11.1) + multipart-post (2.0.0) nenv (0.2.0) nokogiri (1.6.6.2) mini_portile (~> 0.6.0) @@ -193,6 +206,7 @@ GEM rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.4) ruby-progressbar (1.7.5) + safe_yaml (1.0.4) shellany (0.0.1) simplecov (0.10.0) docile (~> 1.1.0) @@ -218,10 +232,15 @@ GEM systemu (2.6.5) thor (0.19.1) thread_safe (0.3.5) + typhoeus (0.7.2) + ethon (>= 0.7.4) tzinfo (1.2.2) thread_safe (~> 0.1) uuid (2.3.8) macaddr (~> 1.0) + webmock (1.21.0) + addressable (>= 2.3.6) + crack (>= 0.3.2) yard (0.8.7.6) PLATFORMS @@ -250,6 +269,7 @@ DEPENDENCIES spring-watcher-listen sqlite3 (~> 1.3.10) uuid (~> 2.3.8) + webmock (~> 1.21.0) yard BUNDLED WITH diff --git a/diaspora_federation.gemspec b/diaspora_federation.gemspec index 77d9274..720ebc9 100644 --- a/diaspora_federation.gemspec +++ b/diaspora_federation.gemspec @@ -21,4 +21,7 @@ Gem::Specification.new do |s| s.required_ruby_version = "~> 2.0" s.add_dependency "nokogiri", "~> 1.6", ">= 1.6.6" + s.add_dependency "faraday", "~> 0.9.0" + s.add_dependency "faraday_middleware", "~> 0.9.0" + s.add_dependency "typhoeus", "~> 0.7.0" end diff --git a/lib/diaspora_federation.rb b/lib/diaspora_federation.rb index 2dbf8c8..312674b 100644 --- a/lib/diaspora_federation.rb +++ b/lib/diaspora_federation.rb @@ -4,6 +4,8 @@ require "diaspora_federation/callbacks" require "diaspora_federation/properties_dsl" require "diaspora_federation/entity" +require "diaspora_federation/fetcher" + require "diaspora_federation/discovery" # diaspora* federation library @@ -30,6 +32,12 @@ module DiasporaFederation # config.server_uri = AppConfig.pod_uri attr_accessor :server_uri + # Set the bundle of certificate authorities (CA) certificates + # + # @example + # config.certificate_authorities = AppConfig.environment.certificate_authorities.get + attr_accessor :certificate_authorities + # configure the federation library # # @example @@ -63,10 +71,17 @@ module DiasporaFederation # called from after_initialize # @raise [ConfigurationError] if the configuration is incomplete or invalid def validate_config - configuration_error "Missing server_uri" unless @server_uri.respond_to? :host + configuration_error "server_uri: Missing or invalid" unless @server_uri.respond_to? :host + + configuration_error "certificate_authorities: Not configured" if @certificate_authorities.nil? + unless File.file? @certificate_authorities + configuration_error "certificate_authorities: File not found: #{@certificate_authorities}" + end + unless @callbacks.definition_complete? configuration_error "Missing handlers for #{@callbacks.missing_handlers.join(', ')}" end + logger.info "successfully configured the federation library" end diff --git a/lib/diaspora_federation/fetcher.rb b/lib/diaspora_federation/fetcher.rb new file mode 100644 index 0000000..4ece8eb --- /dev/null +++ b/lib/diaspora_federation/fetcher.rb @@ -0,0 +1,42 @@ +require "faraday" +require "faraday_middleware/response/follow_redirects" +require "typhoeus/adapters/faraday" + +module DiasporaFederation + # A wrapper for {https://github.com/lostisland/faraday Faraday} used for + # fetching + # + # @see Discovery::Discovery + class Fetcher + # Perform a GET request + # + # @param [String] uri the URI + # @return [Faraday::Response] the response + def self.get(uri) + connection.get(uri) + end + + # gets the Faraday connection + # + # @return [Faraday::Connection] the response + def self.connection + create_default_connection unless @connection + @connection.dup + end + + def self.create_default_connection + options = { + request: {timeout: 30}, + ssl: {ca_file: DiasporaFederation.certificate_authorities} + } + + @connection = Faraday::Connection.new(options) do |builder| + builder.use FaradayMiddleware::FollowRedirects, limit: 4 + builder.adapter :typhoeus + end + + @connection.headers["User-Agent"] = "DiasporaFederation/#{DiasporaFederation::VERSION}" + end + private_class_method :create_default_connection + end +end diff --git a/spec/lib/diaspora_federation/fetcher_spec.rb b/spec/lib/diaspora_federation/fetcher_spec.rb new file mode 100644 index 0000000..a84d819 --- /dev/null +++ b/spec/lib/diaspora_federation/fetcher_spec.rb @@ -0,0 +1,56 @@ +module DiasporaFederation + describe Fetcher do + describe ".get" do + it "gets the url" do + stub_request(:get, "http://www.example.com") + .to_return(body: "foobar", status: 200) + + response = Fetcher.get("http://www.example.com") + expect(response.body).to eq("foobar") + end + + it "follows redirects" do + stub_request(:get, "http://www.example.com") + .to_return(status: 302, headers: {"Location" => "http://www.example.com/redirected"}) + stub_request(:get, "http://www.example.com/redirected") + .to_return(body: "foobar", status: 200) + + response = Fetcher.get("http://www.example.com") + expect(response.body).to eq("foobar") + end + + it "follows redirects 4 times" do + stub_request(:get, "http://www.example.com") + .to_return(status: 302, headers: {"Location" => "http://www.example.com"}).times(4) + .to_return(status: 200) + + Fetcher.get("http://www.example.com") + end + + it "follows redirects not more than 4 times" do + stub_request(:get, "http://www.example.com") + .to_return(status: 302, headers: {"Location" => "http://www.example.com"}) + + expect { Fetcher.get("http://www.example.com") }.to raise_error FaradayMiddleware::RedirectLimitReached + end + + it "uses the gem name as User-Agent" do + stub_request(:get, "http://www.example.com") + .with(headers: {"User-Agent" => "DiasporaFederation/#{DiasporaFederation::VERSION}"}) + + Fetcher.get("http://www.example.com") + end + end + + describe ".connection" do + it "returns a new connection every time" do + expect(Fetcher.connection).to be_a Faraday::Connection + end + + it "returns a new connection every time" do + connection1 = Fetcher.connection + expect(Fetcher.connection).to_not be(connection1) + end + end + end +end diff --git a/spec/lib/diaspora_federation_spec.rb b/spec/lib/diaspora_federation_spec.rb index 72434c5..c875582 100644 --- a/spec/lib/diaspora_federation_spec.rb +++ b/spec/lib/diaspora_federation_spec.rb @@ -6,14 +6,31 @@ module DiasporaFederation DiasporaFederation.validate_config end - it "should fails if the server_uri is missing" do + it "should fail if the server_uri is missing" do temp = DiasporaFederation.server_uri DiasporaFederation.server_uri = nil - expect { DiasporaFederation.validate_config }.to raise_error ConfigurationError, "Missing server_uri" + expect { DiasporaFederation.validate_config }.to raise_error ConfigurationError, + "server_uri: Missing or invalid" DiasporaFederation.server_uri = temp end - it "should validate the config" do + it "should fail if the certificate_authorities is missing" do + temp = DiasporaFederation.certificate_authorities + DiasporaFederation.certificate_authorities = nil + expect { DiasporaFederation.validate_config }.to raise_error ConfigurationError, + "certificate_authorities: Not configured" + DiasporaFederation.certificate_authorities = temp + end + + it "should fail if the certificate_authorities is missing" do + temp = DiasporaFederation.certificate_authorities + DiasporaFederation.certificate_authorities = "/unknown" + expect { DiasporaFederation.validate_config }.to raise_error ConfigurationError, + "certificate_authorities: File not found: /unknown" + DiasporaFederation.certificate_authorities = temp + end + + it "should validate the callbacks" do expect(DiasporaFederation.callbacks).to receive(:definition_complete?).and_return(false) expect { DiasporaFederation.validate_config }.to raise_error ConfigurationError, "Missing handlers for " end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b3e6ca4..9767095 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -18,6 +18,7 @@ ENV["RAILS_ENV"] ||= "test" require File.join(File.dirname(__FILE__), "..", "test", "dummy", "config", "environment") require "rspec/rails" +require "webmock/rspec" # load factory girl factories require "factories" diff --git a/test/dummy/config/initializers/diaspora_federation.rb b/test/dummy/config/initializers/diaspora_federation.rb index 1374fff..190a1ae 100644 --- a/test/dummy/config/initializers/diaspora_federation.rb +++ b/test/dummy/config/initializers/diaspora_federation.rb @@ -1,10 +1,20 @@ require "diaspora_federation/discovery" +if File.file?("/etc/ssl/certs/ca-certificates.crt") + # For Debian, Ubuntu, Archlinux, Gentoo + ca_file = "/etc/ssl/certs/ca-certificates.crt" +else + # For CentOS, Fedora + ca_file = "/etc/pki/tls/certs/ca-bundle.crt" +end + # configure the federation engine DiasporaFederation.configure do |config| # the pod url config.server_uri = URI("http://localhost:3000/") + config.certificate_authorities = ca_file + config.define_callbacks do on :person_webfinger_fetch do |handle| person = Person.find_by(diaspora_handle: handle) From b601d18d9a341a31029551ddeb124cea2d2655e9 Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Mon, 13 Jul 2015 02:16:53 +0200 Subject: [PATCH 03/33] add Discovery class for discovery-logic --- .../discovery/discovery.rb | 77 +++++++++++++++++++ .../discovery/exceptions.rb | 4 + 2 files changed, 81 insertions(+) create mode 100644 lib/diaspora_federation/discovery/discovery.rb diff --git a/lib/diaspora_federation/discovery/discovery.rb b/lib/diaspora_federation/discovery/discovery.rb new file mode 100644 index 0000000..b9e0f6b --- /dev/null +++ b/lib/diaspora_federation/discovery/discovery.rb @@ -0,0 +1,77 @@ +module DiasporaFederation + module Discovery + # This class contains the logic to fetch all data for the given handle + class Discovery + include DiasporaFederation::Logging + + # @return [String] the handle of the account + attr_reader :handle + + # @param [String] account the diaspora handle to discover + def initialize(account) + @handle = clean_handle(account) + end + + # fetch all metadata for the account + def fetch + logger.info "Fetch data for #{handle}" + + unless handle == clean_handle(webfinger.acct_uri) + raise DiscoveryError, "Handle does not match: Wanted #{handle} but got #{clean_handle(webfinger.acct_uri)}" + end + + discovery_data_hash + end + + private + + def clean_handle(account) + account.strip.sub("acct:", "").to_s.downcase + end + + def get(url, http_fallback=false) + logger.info "Fetching #{url} for #{handle}" + response = Fetcher.get(url) + raise "Failed to fetch #{url}: #{response.status}" unless response.success? + response.body + rescue => e + if http_fallback && url.start_with?("https://") + logger.warn "Retry with http: #{url} for #{handle}: #{e.class}: #{e.message}" + url.sub!("https://", "http://") + retry + else + raise DiscoveryError, "Failed to fetch #{url} for #{handle}: #{e.class}: #{e.message}" + end + end + + def host_meta_url + domain = handle.split("@")[1] + "https://#{domain}/.well-known/host-meta" + end + + def legacy_webfinger_url_from_host_meta + # this tries the xrd url with https first, then falls back to http + host_meta = HostMeta.from_xml get(host_meta_url, true) + host_meta.webfinger_template_url.gsub("{uri}", "acct:#{handle}") + end + + def webfinger + @webfinger ||= WebFinger.from_xml get(legacy_webfinger_url_from_host_meta) + end + + def hcard + @hcard ||= HCard.from_html get(webfinger.hcard_url) + end + + def discovery_data_hash + hcard_hash = hcard.to_h.expect(:guid, :serialized_public_key) + hcard_hash.merge( + guid: hcard.guid || webfinger.guid, + public_key: hcard.public_key || webfinger.public_key, + diaspora_handle: handle, + seed_url: webfinger.seed_url + ) + end + end + end +end diff --git a/lib/diaspora_federation/discovery/exceptions.rb b/lib/diaspora_federation/discovery/exceptions.rb index cefced1..38c536a 100644 --- a/lib/diaspora_federation/discovery/exceptions.rb +++ b/lib/diaspora_federation/discovery/exceptions.rb @@ -11,5 +11,9 @@ module DiasporaFederation # * if the html passed to {HCard.from_html} in some way is malformed, invalid or incomplete. class InvalidData < RuntimeError end + + # Raised, if there is an error while discover a new person + class DiscoveryError < RuntimeError + end end end From 3978ea00a1bc15a862545c788c43010cf25fcdda Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Tue, 14 Jul 2015 03:12:54 +0200 Subject: [PATCH 04/33] fix documentation for HCard --- lib/diaspora_federation/discovery/h_card.rb | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/diaspora_federation/discovery/h_card.rb b/lib/diaspora_federation/discovery/h_card.rb index e3f149b..e727e1e 100644 --- a/lib/diaspora_federation/discovery/h_card.rb +++ b/lib/diaspora_federation/discovery/h_card.rb @@ -15,17 +15,17 @@ module DiasporaFederation # # @example Creating a hCard document from a person hash # hc = HCard.new( - # guid: "0123456789abcdef", - # nickname: "user", - # full_name: "User Name", - # seed_url: "https://server.example/", - # photo_large_url: "https://server.example/uploads/l.jpg", - # photo_medium_url: "https://server.example/uploads/m.jpg", - # photo_small_url: "https://server.example/uploads/s.jpg", - # serialized_public_key: "-----BEGIN PUBLIC KEY-----\nABCDEF==\n-----END PUBLIC KEY-----", - # searchable: true, - # first_name: "User", - # last_name: "Name" + # guid: "0123456789abcdef", + # nickname: "user", + # full_name: "User Name", + # seed_url: "https://server.example/", + # photo_large_url: "https://server.example/uploads/l.jpg", + # photo_medium_url: "https://server.example/uploads/m.jpg", + # photo_small_url: "https://server.example/uploads/s.jpg", + # public_key: "-----BEGIN PUBLIC KEY-----\nABCDEF==\n-----END PUBLIC KEY-----", + # searchable: true, + # first_name: "User", + # last_name: "Name" # ) # html_string = hc.to_html # From 35f0af3c55406cf227d77483ed8766331bc39820 Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Tue, 14 Jul 2015 03:32:50 +0200 Subject: [PATCH 05/33] don't parse guid from hcard if it is equals to nickname --- lib/diaspora_federation/discovery/h_card.rb | 12 ++++++++++-- .../lib/diaspora_federation/discovery/h_card_spec.rb | 3 +++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/diaspora_federation/discovery/h_card.rb b/lib/diaspora_federation/discovery/h_card.rb index e727e1e..ceb1373 100644 --- a/lib/diaspora_federation/discovery/h_card.rb +++ b/lib/diaspora_federation/discovery/h_card.rb @@ -164,7 +164,7 @@ module DiasporaFederation doc = parse_html_and_validate(html_string) data = { - guid: content_from_doc(doc, :uid), + guid: guid_from_doc(doc), nickname: content_from_doc(doc, :nickname), full_name: content_from_doc(doc, :fn), url: element_from_doc(doc, :url)["href"], @@ -173,7 +173,7 @@ module DiasporaFederation photo_small_url: photo_from_doc(doc, :photo_small), searchable: (content_from_doc(doc, :searchable) == "true"), - # TODO: change me! ################### + # TODO: remove me! ################### first_name: content_from_doc(doc, :given_name), last_name: content_from_doc(doc, :family_name) ####################################### @@ -288,6 +288,14 @@ module DiasporaFederation element_from_doc(doc, photo_selector)["src"] end private_class_method :photo_from_doc + + # @deprecated hack for old hcard + # @todo remove this when all pods have the new generator + def self.guid_from_doc(doc) + uid_element = element_from_doc(doc, :uid) + uid_element.content unless uid_element[:class].include? "nickname" + end + private_class_method :guid_from_doc end end end diff --git a/spec/lib/diaspora_federation/discovery/h_card_spec.rb b/spec/lib/diaspora_federation/discovery/h_card_spec.rb index 3e763d2..f4cf1c4 100644 --- a/spec/lib/diaspora_federation/discovery/h_card_spec.rb +++ b/spec/lib/diaspora_federation/discovery/h_card_spec.rb @@ -227,6 +227,9 @@ HTML expect(hcard.first_name).to eq(person.first_name) expect(hcard.last_name).to eq(person.last_name) + + expect(hcard.guid).to be_nil + expect(hcard.public_key).to be_nil end it "fails if the document is incomplete" do From 94f9fe89c1ad325b60564b2a408ea0e547be2759 Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Tue, 14 Jul 2015 23:45:56 +0200 Subject: [PATCH 06/33] parse webfinger without guid and public key --- lib/diaspora_federation/discovery/h_card.rb | 8 ++--- .../discovery/web_finger.rb | 36 +++++++------------ .../discovery/web_finger_spec.rb | 29 ++++++++++++++- 3 files changed, 44 insertions(+), 29 deletions(-) diff --git a/lib/diaspora_federation/discovery/h_card.rb b/lib/diaspora_federation/discovery/h_card.rb index ceb1373..d2e4e30 100644 --- a/lib/diaspora_federation/discovery/h_card.rb +++ b/lib/diaspora_federation/discovery/h_card.rb @@ -70,10 +70,8 @@ module DiasporaFederation # DER-encoded PKCS#1 key beginning with the text # "-----BEGIN PUBLIC KEY-----" and ending with "-----END PUBLIC KEY-----". # - # @note the public key is new in the hcard and is optional now. - # # @return [String] public key - property :public_key, default: nil + property :public_key # @!attribute [r] photo_large_url # @return [String] url to the big avatar (300x300) @@ -172,14 +170,14 @@ module DiasporaFederation photo_medium_url: photo_from_doc(doc, :photo_medium), photo_small_url: photo_from_doc(doc, :photo_small), searchable: (content_from_doc(doc, :searchable) == "true"), + # TODO: public key is new and can be missing + public_key: (content_from_doc(doc, :key) unless element_from_doc(doc, :key).nil?), # TODO: remove me! ################### first_name: content_from_doc(doc, :given_name), last_name: content_from_doc(doc, :family_name) ####################################### } - # TODO: public key is new and can be missing - data[:public_key] = content_from_doc(doc, :key) unless element_from_doc(doc, :key).nil? new(data) end diff --git a/lib/diaspora_federation/discovery/web_finger.rb b/lib/diaspora_federation/discovery/web_finger.rb index ddeabd1..a500d43 100644 --- a/lib/diaspora_federation/discovery/web_finger.rb +++ b/lib/diaspora_federation/discovery/web_finger.rb @@ -147,20 +147,23 @@ module DiasporaFederation def self.from_xml(webfinger_xml) data = parse_xml_and_validate(webfinger_xml) - hcard_url, seed_url, guid, profile_url, atom_url, salmon_url, public_key = parse_links(data) + links = data[:links] + + # TODO: remove! public key is deprecated in webfinger + public_key = parse_link(links, REL_PUBKEY) new( acct_uri: data[:subject], alias_url: data[:aliases].first, - hcard_url: hcard_url, - seed_url: seed_url, - profile_url: profile_url, - atom_url: atom_url, - salmon_url: salmon_url, + hcard_url: parse_link(links, REL_HCARD), + seed_url: parse_link(links, REL_SEED), + profile_url: parse_link(links, REL_PROFILE), + atom_url: parse_link(links, REL_ATOM), + salmon_url: parse_link(links, REL_SALMON), # TODO: remove me! ########## - guid: guid, - public_key: Base64.strict_decode64(public_key) + guid: parse_link(links, REL_GUID), + public_key: (Base64.strict_decode64(public_key) if public_key) ) end @@ -209,22 +212,9 @@ module DiasporaFederation ################################## end - def self.parse_links(data) - links = data[:links] - hcard = parse_link(links, REL_HCARD) - seed = parse_link(links, REL_SEED) - guid = parse_link(links, REL_GUID) - profile = parse_link(links, REL_PROFILE) - atom = parse_link(links, REL_ATOM) - salmon = parse_link(links, REL_SALMON) - pubkey = parse_link(links, REL_PUBKEY) - raise InvalidData, "webfinger xml is incomplete" unless [hcard, seed, guid, profile, atom, salmon, pubkey].all? - [hcard[:href], seed[:href], guid[:href], profile[:href], atom[:href], salmon[:href], pubkey[:href]] - end - private_class_method :parse_links - def self.parse_link(links, rel) - links.find {|l| l[:rel] == rel } + element = links.find {|l| l[:rel] == rel } + element ? element[:href] : nil end private_class_method :parse_link end diff --git a/spec/lib/diaspora_federation/discovery/web_finger_spec.rb b/spec/lib/diaspora_federation/discovery/web_finger_spec.rb index 340e321..a2d24a7 100644 --- a/spec/lib/diaspora_federation/discovery/web_finger_spec.rb +++ b/spec/lib/diaspora_federation/discovery/web_finger_spec.rb @@ -97,8 +97,35 @@ XML expect(wf.public_key).to eq(person.serialized_public_key) end + it "reads future XML without guid and public key" do + future_xml = <<-XML + + + #{acct} + #{person.alias_url} + + + + + + +XML + + wf = Discovery::WebFinger.from_xml(future_xml) + expect(wf.acct_uri).to eq(acct) + expect(wf.alias_url).to eq(person.alias_url) + expect(wf.hcard_url).to eq(person.hcard_url) + expect(wf.seed_url).to eq(person.url) + expect(wf.profile_url).to eq(person.profile_url) + expect(wf.atom_url).to eq(person.atom_url) + expect(wf.salmon_url).to eq(person.salmon_url) + + expect(wf.guid).to be_nil + expect(wf.public_key).to be_nil + end + it "fails if the document is empty" do - invalid_xml = < From 6d63903e7370bec287fc3c4cec54211b19114fcb Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Wed, 15 Jul 2015 00:07:09 +0200 Subject: [PATCH 07/33] set nil if parsing an empty string --- lib/diaspora_federation/entity.rb | 7 ++++++- .../discovery/h_card_spec.rb | 19 +++++++++++++++++++ spec/lib/diaspora_federation/entity_spec.rb | 13 ++++++++++--- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/lib/diaspora_federation/entity.rb b/lib/diaspora_federation/entity.rb index fa3793f..814d447 100644 --- a/lib/diaspora_federation/entity.rb +++ b/lib/diaspora_federation/entity.rb @@ -50,7 +50,7 @@ module DiasporaFederation end self.class.default_values.merge(data).each do |k, v| - instance_variable_set("@#{k}", v) if setable?(k, v) + instance_variable_set("@#{k}", nilify(v)) if setable?(k, v) end freeze end @@ -113,6 +113,11 @@ module DiasporaFederation val.all? {|v| v.instance_of?(t.first) }) end + def nilify(value) + return nil if value.respond_to?(:empty?) && value.empty? + value + end + # Serialize the Entity into XML elements # @return [Nokogiri::XML::Element] root node def entity_xml diff --git a/spec/lib/diaspora_federation/discovery/h_card_spec.rb b/spec/lib/diaspora_federation/discovery/h_card_spec.rb index f4cf1c4..507a265 100644 --- a/spec/lib/diaspora_federation/discovery/h_card_spec.rb +++ b/spec/lib/diaspora_federation/discovery/h_card_spec.rb @@ -152,6 +152,25 @@ HTML expect(hcard.searchable).to eq(false) end + it "name is nil if empty" do + changed_html = html.sub( + "class=\"fn\">#{person.full_name}<", + "class=\"fn\"><" + ).sub( + "class=\"given_name\">#{person.first_name}<", + "class=\"given_name\"><" + ).sub( + "class=\"family_name\">#{person.last_name}<", + "class=\"family_name\"><" + ) + + hcard = Discovery::HCard.from_html(changed_html) + + expect(hcard.full_name).to be_nil + expect(hcard.first_name).to be_nil + expect(hcard.last_name).to be_nil + end + it "reads old-style HTML" do historic_html = <<-HTML
diff --git a/spec/lib/diaspora_federation/entity_spec.rb b/spec/lib/diaspora_federation/entity_spec.rb index 10192e5..408ebed 100644 --- a/spec/lib/diaspora_federation/entity_spec.rb +++ b/spec/lib/diaspora_federation/entity_spec.rb @@ -20,17 +20,24 @@ module DiasporaFederation it "sets the defaults" do entity = Entities::TestDefaultEntity.new(test1: 1, test2: 2) - expect(entity.to_h[:test3]).to be_truthy + expect(entity.test3).to be_truthy end it "handles callable defaults" do entity = Entities::TestDefaultEntity.new(test1: 1, test2: 2) - expect(entity.to_h[:test4]).to be_truthy + expect(entity.test4).to be_truthy end it "uses provided values over defaults" do entity = Entities::TestDefaultEntity.new(data) - expect(entity.to_h[:test3]).to be_falsey + expect(entity.test3).to be_falsey + expect(entity.test4).to be_falsey + end + + it "sets nil if string is empty" do + data[:test1] = "" + entity = Entities::TestDefaultEntity.new(data) + expect(entity.test1).to be_nil end end From c65ad965b3247ead8d96d67aeb3f990199927d84 Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Thu, 16 Jul 2015 21:43:21 +0200 Subject: [PATCH 08/33] add Person and Profile entity for discovery-data --- lib/diaspora_federation.rb | 2 + .../discovery/discovery.rb | 27 +++++++--- lib/diaspora_federation/discovery/h_card.rb | 2 +- lib/diaspora_federation/entities.rb | 12 +++++ lib/diaspora_federation/entities/person.rb | 31 +++++++++++ lib/diaspora_federation/entities/profile.rb | 51 +++++++++++++++++++ spec/entities.rb | 36 ++++++------- 7 files changed, 136 insertions(+), 25 deletions(-) create mode 100644 lib/diaspora_federation/entities.rb create mode 100644 lib/diaspora_federation/entities/person.rb create mode 100644 lib/diaspora_federation/entities/profile.rb diff --git a/lib/diaspora_federation.rb b/lib/diaspora_federation.rb index 312674b..8ccd903 100644 --- a/lib/diaspora_federation.rb +++ b/lib/diaspora_federation.rb @@ -6,6 +6,8 @@ require "diaspora_federation/entity" require "diaspora_federation/fetcher" +require "diaspora_federation/entities" + require "diaspora_federation/discovery" # diaspora* federation library diff --git a/lib/diaspora_federation/discovery/discovery.rb b/lib/diaspora_federation/discovery/discovery.rb index b9e0f6b..82bbb92 100644 --- a/lib/diaspora_federation/discovery/discovery.rb +++ b/lib/diaspora_federation/discovery/discovery.rb @@ -13,6 +13,7 @@ module DiasporaFederation end # fetch all metadata for the account + # @return [Person] def fetch logger.info "Fetch data for #{handle}" @@ -20,7 +21,7 @@ module DiasporaFederation raise DiscoveryError, "Handle does not match: Wanted #{handle} but got #{clean_handle(webfinger.acct_uri)}" end - discovery_data_hash + Entities::Person.new(person_hash) end private @@ -63,14 +64,26 @@ module DiasporaFederation @hcard ||= HCard.from_html get(webfinger.hcard_url) end - def discovery_data_hash - hcard_hash = hcard.to_h.expect(:guid, :serialized_public_key) - hcard_hash.merge( + def person_hash + { guid: hcard.guid || webfinger.guid, - public_key: hcard.public_key || webfinger.public_key, diaspora_handle: handle, - seed_url: webfinger.seed_url - ) + url: webfinger.seed_url, + exported_key: hcard.public_key || webfinger.public_key, + profile: Entities::Profile.new(profile_hash) + } + end + + def profile_hash + { + diaspora_handle: handle, + first_name: hcard.first_name, + last_name: hcard.last_name, + image_url: hcard.photo_large_url, + image_url_medium: hcard.photo_medium_url, + image_url_small: hcard.photo_small_url, + searchable: hcard.searchable + } end end end diff --git a/lib/diaspora_federation/discovery/h_card.rb b/lib/diaspora_federation/discovery/h_card.rb index d2e4e30..5a247af 100644 --- a/lib/diaspora_federation/discovery/h_card.rb +++ b/lib/diaspora_federation/discovery/h_card.rb @@ -53,7 +53,7 @@ module DiasporaFederation property :nickname # @!attribute [r] full_name - # @return [String] display name of the user + # @return [String] display name of the user property :full_name # @!attribute [r] url diff --git a/lib/diaspora_federation/entities.rb b/lib/diaspora_federation/entities.rb new file mode 100644 index 0000000..3665875 --- /dev/null +++ b/lib/diaspora_federation/entities.rb @@ -0,0 +1,12 @@ +module DiasporaFederation + # This namespace contains all the entities used to encapsulate data that is + # passed around in the Diaspora* network as part of the federation protocol. + # + # All entities must be defined in this namespace. otherwise the XML + # de-serialization will fail. + module Entities + end +end + +require "diaspora_federation/entities/profile" +require "diaspora_federation/entities/person" diff --git a/lib/diaspora_federation/entities/person.rb b/lib/diaspora_federation/entities/person.rb new file mode 100644 index 0000000..4dd2f09 --- /dev/null +++ b/lib/diaspora_federation/entities/person.rb @@ -0,0 +1,31 @@ +module DiasporaFederation + module Entities + # this entity contains the base data of a person + class Person < Entity + # @!attribute [r] guid + # @see HCard#guid + # @return [String] guid + property :guid + + # @!attribute [r] diaspora_handle + # The diaspora handle of the person + # @return [String] diaspora handle + property :diaspora_handle + + # @!attribute [r] url + # @see WebFinger#seed_url + # @return [String] link to the pod + property :url + + # @!attribute [r] profile + # all profile data of the person + # @return [Profile] the profile of the person + entity :profile, Entities::Profile + + # @!attribute [r] exported_key + # @see HCard#public_key + # @return [String] public key + property :exported_key + end + end +end diff --git a/lib/diaspora_federation/entities/profile.rb b/lib/diaspora_federation/entities/profile.rb new file mode 100644 index 0000000..4c89671 --- /dev/null +++ b/lib/diaspora_federation/entities/profile.rb @@ -0,0 +1,51 @@ +module DiasporaFederation + module Entities + # this entity contains all the profile data of a person + class Profile < Entity + # @!attribute [r] diaspora_handle + # The diaspora handle of the person + # @see Person#diaspora_handle + # @return [String] diaspora handle + property :diaspora_handle + + # @!attribute [r] first_name + # @deprecated + # @see #full_name + # @see HCard#first_name + # @return [String] first name + property :first_name, default: nil + + # @!attribute [r] last_name + # @deprecated + # @see #full_name + # @see HCard#last_name + # @return [String] last name + property :last_name, default: nil + # @!attribute [r] image_url + # @see HCard#photo_large_url + # @return [String] url to the big avatar (300x300) + property :image_url, default: nil + # @!attribute [r] image_url_medium + # @see HCard#photo_medium_url + # @return [String] url to the medium avatar (100x100) + property :image_url_medium, default: nil + # @!attribute [r] image_url_small + # @see HCard#photo_small_url + # @return [String] url to the small avatar (50x50) + property :image_url_small, default: nil + + property :birthday, default: nil + property :gender, default: nil + property :bio, default: nil + property :location, default: nil + + # @!attribute [r] searchable + # @see HCard#searchable + # @return [Boolean] searchable flag + property :searchable, default: true + + property :nsfw, default: false + property :tag_string, default: nil + end + end +end diff --git a/spec/entities.rb b/spec/entities.rb index 486a8a6..ee8f312 100644 --- a/spec/entities.rb +++ b/spec/entities.rb @@ -1,22 +1,24 @@ -module Entities - class TestEntity < DiasporaFederation::Entity - property :test - end +module DiasporaFederation + module Entities + class TestEntity < DiasporaFederation::Entity + property :test + end - class TestDefaultEntity < DiasporaFederation::Entity - property :test1 - property :test2 - property :test3, default: true - property :test4, default: -> { true } - end + class TestDefaultEntity < DiasporaFederation::Entity + property :test1 + property :test2 + property :test3, default: true + property :test4, default: -> { true } + end - class OtherEntity < DiasporaFederation::Entity - property :asdf - end + class OtherEntity < DiasporaFederation::Entity + property :asdf + end - class TestNestedEntity < DiasporaFederation::Entity - property :asdf - entity :test, TestEntity - entity :multi, [OtherEntity] + class TestNestedEntity < DiasporaFederation::Entity + property :asdf + entity :test, TestEntity + entity :multi, [OtherEntity] + end end end From 3a94930dcdb2175fe75147db964f7ff427a249da Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Fri, 17 Jul 2015 23:41:21 +0200 Subject: [PATCH 09/33] add test for Discovery --- lib/diaspora_federation/discovery.rb | 1 + .../discovery/discovery_spec.rb | 192 ++++++++++++++++++ spec/spec_helper.rb | 3 - spec/support/fixture_generation.rb | 6 + 4 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 spec/lib/diaspora_federation/discovery/discovery_spec.rb diff --git a/lib/diaspora_federation/discovery.rb b/lib/diaspora_federation/discovery.rb index d917b32..95cd7e9 100644 --- a/lib/diaspora_federation/discovery.rb +++ b/lib/diaspora_federation/discovery.rb @@ -11,3 +11,4 @@ require "diaspora_federation/discovery/xrd_document" require "diaspora_federation/discovery/host_meta" require "diaspora_federation/discovery/web_finger" require "diaspora_federation/discovery/h_card" +require "diaspora_federation/discovery/discovery" diff --git a/spec/lib/diaspora_federation/discovery/discovery_spec.rb b/spec/lib/diaspora_federation/discovery/discovery_spec.rb new file mode 100644 index 0000000..f209495 --- /dev/null +++ b/spec/lib/diaspora_federation/discovery/discovery_spec.rb @@ -0,0 +1,192 @@ +module DiasporaFederation + describe Discovery::Discovery do + let(:host_meta_xrd) { FixtureGeneration.load_fixture("host-meta") } + let(:webfinger_xrd) { FixtureGeneration.load_fixture("legacy-webfinger") } + let(:hcard_html) { FixtureGeneration.load_fixture("hcard") } + let(:account) { alice.diaspora_handle } + let(:default_image) { "http://localhost:3000/assets/user/default.png" } + + describe "#intialize" do + it "sets handle" do + discovery = Discovery::Discovery.new("some_user@example.com") + expect(discovery.handle).to eq("some_user@example.com") + end + + it "downcases account and strips whitespace, and sub 'acct:'" do + discovery = Discovery::Discovery.new("acct:BIGBOY@Example.Com ") + expect(discovery.handle).to eq("bigboy@example.com") + end + end + + describe ".fetch" do + it "fetches the userdata and returns a person object" do + stub_request(:get, "https://localhost:3000/.well-known/host-meta") + .to_return(status: 200, body: host_meta_xrd) + stub_request(:get, "http://localhost:3000/webfinger?q=acct:#{account}") + .to_return(status: 200, body: webfinger_xrd) + stub_request(:get, "http://localhost:3000/hcard/users/#{alice.guid}") + .to_return(status: 200, body: hcard_html) + + person = Discovery::Discovery.new(account).fetch + + expect(person.guid).to eq(alice.guid) + expect(person.diaspora_handle).to eq(account) + expect(person.url).to eq(alice.url) + expect(person.exported_key).to eq(alice.serialized_public_key) + + profile = person.profile + + expect(profile.diaspora_handle).to eq(alice.diaspora_handle) + expect(profile.first_name).to eq("Dummy") + expect(profile.last_name).to eq("User") + + expect(profile.image_url).to eq(default_image) + expect(profile.image_url_medium).to eq(default_image) + expect(profile.image_url_small).to eq(default_image) + end + + it "falls back to http if https fails with 404" do + stub_request(:get, "https://localhost:3000/.well-known/host-meta") + .to_return(status: 404) + stub_request(:get, "http://localhost:3000/.well-known/host-meta") + .to_return(status: 200, body: host_meta_xrd) + stub_request(:get, "http://localhost:3000/webfinger?q=acct:#{account}") + .to_return(status: 200, body: webfinger_xrd) + stub_request(:get, "http://localhost:3000/hcard/users/#{alice.guid}") + .to_return(status: 200, body: hcard_html) + + person = Discovery::Discovery.new(account).fetch + + expect(person.guid).to eq(alice.guid) + expect(person.diaspora_handle).to eq(account) + end + + it "falls back to http if https fails with ssl error" do + stub_request(:get, "https://localhost:3000/.well-known/host-meta") + .to_raise(OpenSSL::SSL::SSLError) + stub_request(:get, "http://localhost:3000/.well-known/host-meta") + .to_return(status: 200, body: host_meta_xrd) + stub_request(:get, "http://localhost:3000/webfinger?q=acct:#{account}") + .to_return(status: 200, body: webfinger_xrd) + stub_request(:get, "http://localhost:3000/hcard/users/#{alice.guid}") + .to_return(status: 200, body: hcard_html) + + person = Discovery::Discovery.new(account).fetch + + expect(person.guid).to eq(alice.guid) + expect(person.diaspora_handle).to eq(account) + end + + it "fails if the handle does not match" do + modified_webfinger = webfinger_xrd.gsub(account, "anonther_user@example.com") + + stub_request(:get, "https://localhost:3000/.well-known/host-meta") + .to_return(status: 200, body: host_meta_xrd) + stub_request(:get, "http://localhost:3000/webfinger?q=acct:#{account}") + .to_return(status: 200, body: modified_webfinger) + + expect { Discovery::Discovery.new(account).fetch }.to raise_error Discovery::DiscoveryError + end + + it "fails if the handle was not found" do + stub_request(:get, "https://localhost:3000/.well-known/host-meta") + .to_return(status: 200, body: host_meta_xrd) + stub_request(:get, "http://localhost:3000/webfinger?q=acct:#{account}") + .to_return(status: 404) + + expect { Discovery::Discovery.new(account).fetch }.to raise_error Discovery::DiscoveryError + end + + it "reads old hcard without guid and public key" do + historic_hcard_html = <<-HTML +
+

#{account}

+
+
+

User profile

+
+
Nickname
+
+ +
+
+
+
First name
+
+ +
+
+
+
Family name
+
+ +
+
+
+
Full name
+
+ +
+
+
+
URL
+
+#{alice.url} +
+
+
+
Photo
+
+ +
+
+
+
Photo
+
+ +
+
+
+
Photo
+
+ +
+
+
+
Searchable
+
+true +
+
+
+
+
+ HTML + + stub_request(:get, "https://localhost:3000/.well-known/host-meta") + .to_return(status: 200, body: host_meta_xrd) + stub_request(:get, "http://localhost:3000/webfinger?q=acct:#{account}") + .to_return(status: 200, body: webfinger_xrd) + stub_request(:get, "http://localhost:3000/hcard/users/#{alice.guid}") + .to_return(status: 200, body: historic_hcard_html) + + person = Discovery::Discovery.new(account).fetch + + expect(person.guid).to eq(alice.guid) + expect(person.diaspora_handle).to eq(account) + expect(person.url).to eq(alice.url) + expect(person.exported_key).to eq(alice.serialized_public_key) + + profile = person.profile + + expect(profile.diaspora_handle).to eq(alice.diaspora_handle) + expect(profile.first_name).to be_nil + expect(profile.last_name).to be_nil + + expect(profile.image_url).to eq(default_image) + expect(profile.image_url_medium).to eq(default_image) + expect(profile.image_url_small).to eq(default_image) + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9767095..4d25d10 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -32,9 +32,6 @@ def alice @alice ||= Person.find_by(diaspora_handle: "alice@localhost:3000") end -# Force fixture rebuild -FileUtils.rm_f(Rails.root.join("tmp", "fixture_builder.yml")) - # Requires supporting files with custom matchers and macros, etc, # in ./support/ and its subdirectories. fixture_builder_file = "#{File.dirname(__FILE__)}/support/fixture_builder.rb" diff --git a/spec/support/fixture_generation.rb b/spec/support/fixture_generation.rb index d7081e7..e06dec1 100644 --- a/spec/support/fixture_generation.rb +++ b/spec/support/fixture_generation.rb @@ -9,6 +9,12 @@ module FixtureGeneration file.puts(markup) end end + + def self.load_fixture(name, fixture_path=nil) + fixture_path = Rails.root.join("tmp", "fixtures") unless fixture_path + fixture_file = fixture_path.join("#{name}.fixture.html") + File.open(fixture_file).read + end end RSpec::Rails::ControllerExampleGroup.class_eval do From 7be77154dc258d2615a9b424d2ab6c19b8d77e20 Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Sat, 18 Jul 2015 23:36:20 +0200 Subject: [PATCH 10/33] add valid gem for validation --- Gemfile.lock | 2 ++ diaspora_federation.gemspec | 1 + 2 files changed, 3 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index 7eac604..d1f992c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,6 +6,7 @@ PATH faraday_middleware (~> 0.9.0) nokogiri (~> 1.6, >= 1.6.6) typhoeus (~> 0.7.0) + valid (~> 0.5.0) diaspora_federation-rails (0.0.3) diaspora_federation (= 0.0.3) rails (~> 4.2) @@ -238,6 +239,7 @@ GEM thread_safe (~> 0.1) uuid (2.3.8) macaddr (~> 1.0) + valid (0.5.0) webmock (1.21.0) addressable (>= 2.3.6) crack (>= 0.3.2) diff --git a/diaspora_federation.gemspec b/diaspora_federation.gemspec index 720ebc9..6e58eae 100644 --- a/diaspora_federation.gemspec +++ b/diaspora_federation.gemspec @@ -24,4 +24,5 @@ Gem::Specification.new do |s| s.add_dependency "faraday", "~> 0.9.0" s.add_dependency "faraday_middleware", "~> 0.9.0" s.add_dependency "typhoeus", "~> 0.7.0" + s.add_dependency "valid", "~> 0.5.0" end From 2301b1433ea5259a5eca54b0e454120963c9e58e Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Sun, 19 Jul 2015 17:26:41 +0200 Subject: [PATCH 11/33] add validators from raven24 --- lib/diaspora_federation.rb | 1 + lib/diaspora_federation/validators.rb | 32 +++++++ .../validators/person_validator.rb | 17 ++++ .../validators/profile_validator.rb | 22 +++++ .../validators/rules/birthday.rb | 30 ++++++ .../validators/rules/boolean.rb | 30 ++++++ .../validators/rules/guid.rb | 20 ++++ .../validators/rules/not_nil.rb | 20 ++++ .../validators/rules/public_key.rb | 24 +++++ .../validators/rules/tag_count.rb | 27 ++++++ spec/factories.rb | 28 +++++- .../validators/person_validator_spec.rb | 41 ++++++++ .../validators/profile_validator_spec.rb | 94 +++++++++++++++++++ .../validators/rules/birthday_spec.rb | 50 ++++++++++ .../validators/rules/boolean_spec.rb | 70 ++++++++++++++ .../validators/rules/guid_spec.rb | 59 ++++++++++++ .../validators/rules/public_key_spec.rb | 51 ++++++++++ .../validators/rules/tag_count_spec.rb | 36 +++++++ spec/support/shared_validator_specs.rb | 85 +++++++++++++++++ 19 files changed, 735 insertions(+), 2 deletions(-) create mode 100644 lib/diaspora_federation/validators.rb create mode 100644 lib/diaspora_federation/validators/person_validator.rb create mode 100644 lib/diaspora_federation/validators/profile_validator.rb create mode 100644 lib/diaspora_federation/validators/rules/birthday.rb create mode 100644 lib/diaspora_federation/validators/rules/boolean.rb create mode 100644 lib/diaspora_federation/validators/rules/guid.rb create mode 100644 lib/diaspora_federation/validators/rules/not_nil.rb create mode 100644 lib/diaspora_federation/validators/rules/public_key.rb create mode 100644 lib/diaspora_federation/validators/rules/tag_count.rb create mode 100644 spec/lib/diaspora_federation/validators/person_validator_spec.rb create mode 100644 spec/lib/diaspora_federation/validators/profile_validator_spec.rb create mode 100644 spec/lib/diaspora_federation/validators/rules/birthday_spec.rb create mode 100644 spec/lib/diaspora_federation/validators/rules/boolean_spec.rb create mode 100644 spec/lib/diaspora_federation/validators/rules/guid_spec.rb create mode 100644 spec/lib/diaspora_federation/validators/rules/public_key_spec.rb create mode 100644 spec/lib/diaspora_federation/validators/rules/tag_count_spec.rb create mode 100644 spec/support/shared_validator_specs.rb diff --git a/lib/diaspora_federation.rb b/lib/diaspora_federation.rb index 8ccd903..ebbb1ef 100644 --- a/lib/diaspora_federation.rb +++ b/lib/diaspora_federation.rb @@ -3,6 +3,7 @@ require "diaspora_federation/logging" require "diaspora_federation/callbacks" require "diaspora_federation/properties_dsl" require "diaspora_federation/entity" +require "diaspora_federation/validators" require "diaspora_federation/fetcher" diff --git a/lib/diaspora_federation/validators.rb b/lib/diaspora_federation/validators.rb new file mode 100644 index 0000000..a5f0e86 --- /dev/null +++ b/lib/diaspora_federation/validators.rb @@ -0,0 +1,32 @@ +require "validation" +require "validation/rule/not_empty" +require "validation/rule/email" +require "validation/rule/regular_expression" +require "validation/rule/uri" + +# +valid+ gem namespace +module Validation + # This module contains custom validation rules for various data field types. + # That includes types for which there are no provided rules by the +valid+ gem + # or types that are very specific to Diaspora* federation and need special handling. + # The rules are used inside the {DiasporaFederation::Validators validator classes} + # to perform basic sanity-checks on {DiasporaFederation::Entities federation entities}. + module Rule + end +end + +require "diaspora_federation/validators/rules/birthday" +require "diaspora_federation/validators/rules/boolean" +require "diaspora_federation/validators/rules/guid" +require "diaspora_federation/validators/rules/not_nil" +require "diaspora_federation/validators/rules/public_key" +require "diaspora_federation/validators/rules/tag_count" + +module DiasporaFederation + # Validators to perform basic sanity-checks on {DiasporaFederation::Entities federation entities}. + module Validators + end +end + +require "diaspora_federation/validators/person_validator" +require "diaspora_federation/validators/profile_validator" diff --git a/lib/diaspora_federation/validators/person_validator.rb b/lib/diaspora_federation/validators/person_validator.rb new file mode 100644 index 0000000..1e58117 --- /dev/null +++ b/lib/diaspora_federation/validators/person_validator.rb @@ -0,0 +1,17 @@ +module DiasporaFederation + module Validators + class PersonValidator < Validation::Validator + include Validation + + rule :guid, :guid + + rule :diaspora_handle, %i(not_empty email) + + rule :url, :u_r_i # WTF? :uri -> Uri -> "uninitialized constant Uri", :u_r_i -> URI -> \o/ + + rule :profile, :not_nil + + rule :exported_key, :public_key + end + end +end diff --git a/lib/diaspora_federation/validators/profile_validator.rb b/lib/diaspora_federation/validators/profile_validator.rb new file mode 100644 index 0000000..1e6cfa8 --- /dev/null +++ b/lib/diaspora_federation/validators/profile_validator.rb @@ -0,0 +1,22 @@ +module DiasporaFederation + module Validators + class ProfileValidator < Validation::Validator + include Validation + + rule :diaspora_handle, %i(not_empty email) + + # the name must not contain a semicolon because of mentions + # @{ ; } + rule :first_name, regular_expression: {regex: /\A[^;]{,32}\z/} + rule :last_name, regular_expression: {regex: /\A[^;]{,32}\z/} + + rule :tag_string, tag_count: {maximum: 5} + + rule :birthday, :birthday + + rule :searchable, :boolean + + rule :nsfw, :boolean + end + end +end diff --git a/lib/diaspora_federation/validators/rules/birthday.rb b/lib/diaspora_federation/validators/rules/birthday.rb new file mode 100644 index 0000000..3562922 --- /dev/null +++ b/lib/diaspora_federation/validators/rules/birthday.rb @@ -0,0 +1,30 @@ +require "date" + +module Validation + module Rule + class Birthday + attr_reader :params + + # no parameters + def initialize + @params = {} + end + + def error_key + :birthday + end + + def valid_value?(value) + return true if value.nil? || (value.is_a?(String) && value.empty?) + return true if value.is_a? Date + + if value =~ /[0-9]{4}\-[0-9]{2}\-[0-9]{2}/ + date_field = value.split("-").map(&:to_i) + return Date.valid_civil?(date_field[0], date_field[1], date_field[2]) + end + + false + end + end + end +end diff --git a/lib/diaspora_federation/validators/rules/boolean.rb b/lib/diaspora_federation/validators/rules/boolean.rb new file mode 100644 index 0000000..f4498df --- /dev/null +++ b/lib/diaspora_federation/validators/rules/boolean.rb @@ -0,0 +1,30 @@ +module Validation + module Rule + class Boolean + attr_reader :params + + # no parameters + def initialize + @params = {} + end + + def error_key + :numeric + end + + def valid_value?(value) + return false if value.nil? + + if value.is_a?(String) + true if value =~ /\A(true|false|t|f|yes|no|y|n|1|0)\z/i + elsif value.is_a?(Fixnum) + true if value == 1 || value == 0 + elsif value.is_a?(TrueClass) || value.is_a?(FalseClass) + true + else + false + end + end + end + end +end diff --git a/lib/diaspora_federation/validators/rules/guid.rb b/lib/diaspora_federation/validators/rules/guid.rb new file mode 100644 index 0000000..7aca5d8 --- /dev/null +++ b/lib/diaspora_federation/validators/rules/guid.rb @@ -0,0 +1,20 @@ +module Validation + module Rule + class Guid + attr_reader :params + + # no parameters + def initialize + @params = {} + end + + def error_key + :guid + end + + def valid_value?(value) + value.is_a?(String) && value.downcase =~ /\A[0-9a-z\-_@.:]{16,}\z/ + end + end + end +end diff --git a/lib/diaspora_federation/validators/rules/not_nil.rb b/lib/diaspora_federation/validators/rules/not_nil.rb new file mode 100644 index 0000000..834384f --- /dev/null +++ b/lib/diaspora_federation/validators/rules/not_nil.rb @@ -0,0 +1,20 @@ +module Validation + module Rule + class NotNil + attr_reader :params + + # no parameters + def initialize + @params = {} + end + + def error_key + :not_nil + end + + def valid_value?(value) + !value.nil? + end + end + end +end diff --git a/lib/diaspora_federation/validators/rules/public_key.rb b/lib/diaspora_federation/validators/rules/public_key.rb new file mode 100644 index 0000000..cc35e07 --- /dev/null +++ b/lib/diaspora_federation/validators/rules/public_key.rb @@ -0,0 +1,24 @@ +module Validation + module Rule + class PublicKey + attr_reader :params + + # no parameters + def initialize + @params = {} + end + + def error_key + :public_key + end + + # allow both "PUBLIC KEY" and "RSA PUBLIC KEY" + def valid_value?(value) + (value.strip.start_with?("-----BEGIN PUBLIC KEY-----") && + value.strip.end_with?("-----END PUBLIC KEY-----")) || + (value.strip.start_with?("-----BEGIN RSA PUBLIC KEY-----") && + value.strip.end_with?("-----END RSA PUBLIC KEY-----")) + end + end + end +end diff --git a/lib/diaspora_federation/validators/rules/tag_count.rb b/lib/diaspora_federation/validators/rules/tag_count.rb new file mode 100644 index 0000000..8f48082 --- /dev/null +++ b/lib/diaspora_federation/validators/rules/tag_count.rb @@ -0,0 +1,27 @@ +module Validation + module Rule + # Rule for validating the number of tags in a string. + # Only the "#" characters will be counted. + class TagCount + attr_reader :params + + # @param [Hash] params + # @option params [Fixnum] :maximum maximum allowed tag count + def initialize(params) + unless params.include?(:maximum) && params[:maximum].is_a?(Fixnum) + raise "A number has to be specified for :maximum" + end + + @params = params + end + + def error_key + :tag_count + end + + def valid_value?(value) + value.count("#") <= params[:maximum] + end + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index 88173c4..79f2b69 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -5,12 +5,36 @@ def r_str end FactoryGirl.define do + sequence(:diaspora_handle) {|n| "person-#{n}-#{r_str}@localhost:3000" } + sequence(:public_key) { OpenSSL::PKey::RSA.generate(1024).public_key.export } + factory :person do - sequence(:diaspora_handle) {|n| "person-#{n}-#{r_str}@localhost:3000" } + diaspora_handle url "http://localhost:3000/" - serialized_public_key OpenSSL::PKey::RSA.generate(1024).public_key.export + serialized_public_key { generate(:public_key) } after(:create) do |u| u.save end end + + factory :person_entity, class: DiasporaFederation::Entities::Person do + guid UUID.generate :compact + diaspora_handle "testing@example.com" + url "http://localhost:3000/" + exported_key { generate(:public_key) } + profile { + DiasporaFederation::Entities::Profile.new( + FactoryGirl.attributes_for(:profile_entity, diaspora_handle: diaspora_handle)) + } + end + + factory :profile_entity, class: DiasporaFederation::Entities::Profile do + diaspora_handle "testing@example.com" + first_name "my_name" + last_name nil + tag_string "#i #love #tags" + birthday "1988-07-15" + searchable true + nsfw false + end end diff --git a/spec/lib/diaspora_federation/validators/person_validator_spec.rb b/spec/lib/diaspora_federation/validators/person_validator_spec.rb new file mode 100644 index 0000000..f954d36 --- /dev/null +++ b/spec/lib/diaspora_federation/validators/person_validator_spec.rb @@ -0,0 +1,41 @@ +module DiasporaFederation + describe Validators::PersonValidator do + it "validates a well-formed instance" do + instance = OpenStruct.new(FactoryGirl.attributes_for(:person_entity)) + validator = Validators::PersonValidator.new(instance) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it_behaves_like "a diaspora_handle validator" do + let(:entity) { :person_entity } + let(:validator_class) { Validators::PersonValidator } + let(:property) { :diaspora_handle } + end + + it_behaves_like "a guid validator" do + let(:entity) { :person_entity } + let(:validator_class) { Validators::PersonValidator } + let(:property) { :guid } + end + + context "#exported_key" do + it "fails for malformed rsa key" do + instance = OpenStruct.new(FactoryGirl.attributes_for(:person_entity, exported_key: "ASDF")) + validator = Validators::PersonValidator.new(instance) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:exported_key) + end + + it "must not be empty" do + instance = OpenStruct.new(FactoryGirl.attributes_for(:person_entity, exported_key: "")) + validator = Validators::PersonValidator.new(instance) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:exported_key) + end + end + end +end diff --git a/spec/lib/diaspora_federation/validators/profile_validator_spec.rb b/spec/lib/diaspora_federation/validators/profile_validator_spec.rb new file mode 100644 index 0000000..ef11474 --- /dev/null +++ b/spec/lib/diaspora_federation/validators/profile_validator_spec.rb @@ -0,0 +1,94 @@ +module DiasporaFederation + describe Validators::ProfileValidator do + def profile_stub(data={}) + OpenStruct.new(FactoryGirl.attributes_for(:profile_entity).merge(data)) + end + + it "validates a well-formed instance" do + validator = Validators::ProfileValidator.new(profile_stub) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it_behaves_like "a diaspora_handle validator" do + let(:entity) { :profile_entity } + let(:validator_class) { Validators::ProfileValidator } + let(:property) { :diaspora_handle } + end + + %i(first_name last_name).each do |prop| + describe "##{prop}" do + it "allowed to contain special chars" do + validator = Validators::ProfileValidator.new(profile_stub(prop => "cool name ©")) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "must not exceed 32 chars" do + validator = Validators::ProfileValidator.new(profile_stub(prop => "abcdefghijklmnopqrstuvwxyz_aaaaaaaaaa")) + + expect(validator).not_to be_valid + expect(validator.errors).to include(prop) + end + + it "must not contain semicolons" do + validator = Validators::ProfileValidator.new(profile_stub(prop => "asdf;qwer;yxcv")) + + expect(validator).not_to be_valid + expect(validator.errors).to include(prop) + end + end + end + + describe "#tag_string" do + it "must not contain more than 5 tags" do + validator = Validators::ProfileValidator.new( + profile_stub(tag_string: "#i #have #too #many #tags #in #my #profile")) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:tag_string) + end + end + + describe "#birthday" do + it "may be empty or nil" do + [nil, ""].each do |val| + validator = Validators::ProfileValidator.new(profile_stub(birthday: val)) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + end + + it "may be a Date or date string" do + [Date.parse("2013-06-29"), "2013-06-29"].each do |val| + validator = Validators::ProfileValidator.new(profile_stub(birthday: val)) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + end + + it "must not be an arbitrary string or other object" do + ["asdf asdf", true, 1234].each do |val| + validator = Validators::ProfileValidator.new(profile_stub(birthday: val)) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:birthday) + end + end + end + + %i(searchable nsfw).each do |prop| + describe "##{prop}" do + it_behaves_like "a boolean validator" do + let(:entity) { :profile_entity } + let(:validator_class) { Validators::ProfileValidator } + let(:property) { prop } + end + end + end + end +end diff --git a/spec/lib/diaspora_federation/validators/rules/birthday_spec.rb b/spec/lib/diaspora_federation/validators/rules/birthday_spec.rb new file mode 100644 index 0000000..e4811cc --- /dev/null +++ b/spec/lib/diaspora_federation/validators/rules/birthday_spec.rb @@ -0,0 +1,50 @@ +describe Validation::Rule::Birthday do + it "will not accept parameters" do + validator = Validation::Validator.new({}) + expect { + validator.rule(:birthday, birthday: {param: true}) + }.to raise_error ArgumentError + end + + context "validation" do + it "validates a date object" do + validator = Validation::Validator.new(OpenStruct.new(birthday: Date.new)) + validator.rule(:birthday, :birthday) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "validates a string" do + validator = Validation::Validator.new(OpenStruct.new(birthday: "2015-07-19")) + validator.rule(:birthday, :birthday) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "validates an empty string" do + validator = Validation::Validator.new(OpenStruct.new(birthday: "")) + validator.rule(:birthday, :birthday) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "validates nil" do + validator = Validation::Validator.new(OpenStruct.new(birthday: nil)) + validator.rule(:birthday, :birthday) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "fails for invalid date string" do + validator = Validation::Validator.new(OpenStruct.new(birthday: "i'm no date")) + validator.rule(:birthday, :birthday) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:birthday) + end + end +end diff --git a/spec/lib/diaspora_federation/validators/rules/boolean_spec.rb b/spec/lib/diaspora_federation/validators/rules/boolean_spec.rb new file mode 100644 index 0000000..dc32f9a --- /dev/null +++ b/spec/lib/diaspora_federation/validators/rules/boolean_spec.rb @@ -0,0 +1,70 @@ +describe Validation::Rule::Boolean do + it "will not accept parameters" do + validator = Validation::Validator.new({}) + expect { + validator.rule(:number, numeric: {param: true}) + }.to raise_error ArgumentError + end + + context "validation" do + context "strings" do + it "validates boolean-esque strings" do + %w(true false yes no t f y n 1 0).each do |str| + validator = Validation::Validator.new(OpenStruct.new(boolean: str)) + validator.rule(:boolean, :boolean) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + end + + it "fails for non-boolean-esque strings" do + validator = Validation::Validator.new(OpenStruct.new(boolean: "asdf")) + validator.rule(:boolean, :boolean) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:boolean) + end + end + + context "numbers" do + it "validates 0 and 1 to boolean" do + [0, 1].each do |num| + validator = Validation::Validator.new(OpenStruct.new(boolean: num)) + validator.rule(:boolean, :boolean) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + end + + it "fails for all other numbers" do + validator = Validation::Validator.new(OpenStruct.new(boolean: 1234)) + validator.rule(:boolean, :boolean) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:boolean) + end + end + + context "boolean types" do + it "validates true and false" do + [true, false].each do |bln| + validator = Validation::Validator.new(OpenStruct.new(boolean: bln)) + validator.rule(:boolean, :boolean) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + end + end + + it "fails for nil" do + validator = Validation::Validator.new(OpenStruct.new(boolean: nil)) + validator.rule(:boolean, :boolean) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:boolean) + end + end +end diff --git a/spec/lib/diaspora_federation/validators/rules/guid_spec.rb b/spec/lib/diaspora_federation/validators/rules/guid_spec.rb new file mode 100644 index 0000000..5cd0b82 --- /dev/null +++ b/spec/lib/diaspora_federation/validators/rules/guid_spec.rb @@ -0,0 +1,59 @@ +describe Validation::Rule::Guid do + it "will not accept parameters" do + validator = Validation::Validator.new({}) + expect { + validator.rule(:guid, guid: {param: true}) + }.to raise_error ArgumentError + end + + context "validation" do + it "validates a string at least 16 chars long, consisting of [0-9a-f] (diaspora)" do + validator = Validation::Validator.new(OpenStruct.new(guid: "abcdef0123456789")) + validator.rule(:guid, :guid) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "validates a long string with random characters and [-_@.:] (redmatrix)" do + validator = Validation::Validator.new( + OpenStruct.new(guid: "1234567890ABCDefgh_ijkl-mnopqrSTUVwxyz@example.com:3000")) + validator.rule(:guid, :guid) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "fails if the string is too short" do + validator = Validation::Validator.new(OpenStruct.new(guid: "012345")) + validator.rule(:guid, :guid) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:guid) + end + + it "fails if the string contains invalid chars" do + validator = Validation::Validator.new(OpenStruct.new(guid: "ghijklmnopqrstuvwxyz++")) + validator.rule(:guid, :guid) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:guid) + end + + it "fails if the string is empty" do + validator = Validation::Validator.new(OpenStruct.new(guid: "")) + validator.rule(:guid, :guid) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:guid) + end + + it "fails if the string is nil" do + validator = Validation::Validator.new(OpenStruct.new(guid: nil)) + validator.rule(:guid, :guid) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:guid) + end + end +end diff --git a/spec/lib/diaspora_federation/validators/rules/public_key_spec.rb b/spec/lib/diaspora_federation/validators/rules/public_key_spec.rb new file mode 100644 index 0000000..5d1ada0 --- /dev/null +++ b/spec/lib/diaspora_federation/validators/rules/public_key_spec.rb @@ -0,0 +1,51 @@ +describe Validation::Rule::Guid do + it "will not accept parameters" do + validator = Validation::Validator.new({}) + expect { + validator.rule(:key, public_key: {param: true}) + }.to raise_error ArgumentError + end + + context "validation" do + ["PUBLIC KEY", "RSA PUBLIC KEY"].each do |key_type| + context key_type do + let(:prefix) { "-----BEGIN #{key_type}-----" } + let(:suffix) { "-----END #{key_type}-----" } + + let(:key) { "#{prefix}\nAAAAAA==\n#{suffix}\n" } + + it "validates an exported RSA key" do + validator = Validation::Validator.new(OpenStruct.new(key: key)) + validator.rule(:key, :public_key) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "strips whitespace" do + validator = Validation::Validator.new(OpenStruct.new(key: " \n #{key}\n \n ")) + validator.rule(:key, :public_key) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "fails if the prefix is missing" do + validator = Validation::Validator.new(OpenStruct.new(key: "\nAAAAAA==\n#{suffix}\n")) + validator.rule(:key, :public_key) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:key) + end + + it "fails if the suffix is missing" do + validator = Validation::Validator.new(OpenStruct.new(key: "#{prefix}\nAAAAAA==\n\n")) + validator.rule(:key, :public_key) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:key) + end + end + end + end +end diff --git a/spec/lib/diaspora_federation/validators/rules/tag_count_spec.rb b/spec/lib/diaspora_federation/validators/rules/tag_count_spec.rb new file mode 100644 index 0000000..32c1aa5 --- /dev/null +++ b/spec/lib/diaspora_federation/validators/rules/tag_count_spec.rb @@ -0,0 +1,36 @@ +describe Validation::Rule::TagCount do + it "requires a parameter" do + validator = Validation::Validator.new({}) + expect { + validator.rule(:tags, :tag_count) + }.to raise_error ArgumentError + end + + context "validation" do + let(:tag_str) { "#i #love #tags" } + + it "validates less tags" do + validator = Validation::Validator.new(OpenStruct.new(tags: tag_str)) + validator.rule(:tags, tag_count: {maximum: 5}) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "validates exactly as many tags" do + validator = Validation::Validator.new(OpenStruct.new(tags: tag_str)) + validator.rule(:tags, tag_count: {maximum: 3}) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "fails for too many tags" do + validator = Validation::Validator.new(OpenStruct.new(tags: tag_str)) + validator.rule(:tags, tag_count: {maximum: 1}) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:tags) + end + end +end diff --git a/spec/support/shared_validator_specs.rb b/spec/support/shared_validator_specs.rb new file mode 100644 index 0000000..7601335 --- /dev/null +++ b/spec/support/shared_validator_specs.rb @@ -0,0 +1,85 @@ +def entity_stub(entity, property, val=nil) + instance = OpenStruct.new(FactoryGirl.attributes_for(entity)) + instance.public_send("#{property}=", val) unless val.nil? + instance +end + +shared_examples "a diaspora_handle validator" do + it "validates a well-formed diaspora_handle" do + validator = validator_class.new(entity_stub(entity, property)) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "must not be empty" do + validator = validator_class.new(entity_stub(entity, property, "")) + + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end + + it "must resemble an email address" do + validator = validator_class.new(entity_stub(entity, property, "i am a weird handle @@@ ### 12345")) + + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end +end + +shared_examples "a guid validator" do + it "validates a well-formed guid" do + validator = validator_class.new(entity_stub(entity, property)) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "validates a well-formed guid from redmatrix" do + validator = validator_class.new(entity_stub(entity, property, "1234567890ABCDefgh_ijkl-mnopQR@example.com:3000")) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "must be at least 16 chars" do + validator = validator_class.new(entity_stub(entity, property, "aaaaaa")) + + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end + + it "must only contain [0-9a-z-_@.:]" do + validator = validator_class.new(entity_stub(entity, property, "zzz+-#*$$")) + + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end + + it "must not be empty" do + validator = validator_class.new(entity_stub(entity, property, "")) + + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end +end + +shared_examples "a boolean validator" do + it "validates a well-formed boolean" do + [true, "true", false, "false"].each do |val| + validator = validator_class.new(entity_stub(entity, property, val)) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + end + + it "must not be an arbitrary string or other object" do + ["asdf", Time.zone.today, 1234].each do |val| + validator = validator_class.new(entity_stub(entity, property, val)) + + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end + end +end From 5be60f8fe03753c8eca5d7af9972b1ed15490fdd Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Mon, 20 Jul 2015 02:49:24 +0200 Subject: [PATCH 12/33] load fixtures before tests --- spec/spec_helper.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4d25d10..1d588a1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -51,6 +51,10 @@ RSpec.configure do |config| config.include FactoryGirl::Syntax::Methods config.use_transactional_fixtures = true + # load fixtures + config.fixture_path = "#{::Rails.root}/test/fixtures" + config.global_fixtures = :all + config.mock_with :rspec do |mocks| # Prevents you from mocking or stubbing a method that does not exist on # a real object. This is generally recommended, and will default to From bfe1b77c8749ab717589b7739ffda345fd49d0a3 Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Mon, 20 Jul 2015 03:05:32 +0200 Subject: [PATCH 13/33] use rails logger and ignore logging for test coverage --- lib/diaspora_federation/logging.rb | 6 ++++-- spec/spec_helper.rb | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/diaspora_federation/logging.rb b/lib/diaspora_federation/logging.rb index 125415c..615e0e2 100644 --- a/lib/diaspora_federation/logging.rb +++ b/lib/diaspora_federation/logging.rb @@ -13,10 +13,12 @@ module DiasporaFederation # use logging-gem if available return ::Logging::Logger[self] if defined?(::Logging::Logger) + # use rails logger if running in rails and no logging-gem is available + return ::Rails.logger if defined?(::Rails) + # fallback logger @logger = Logger.new(STDOUT) - loglevel = defined?(::Rails) ? ::Rails.configuration.log_level.to_s.upcase : "INFO" - @logger.level = Logger.const_get(loglevel) + @logger.level = Logger::INFO @logger end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1d588a1..be9589d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -6,6 +6,7 @@ unless ENV["NO_COVERAGE"] == "true" SimpleCov::Formatter::RcovFormatter ] SimpleCov.start do + add_filter "lib/diaspora_federation/logging.rb" add_filter "spec" add_filter "test" end From 83097572ce707c973d6287528a604d5025081859 Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Mon, 20 Jul 2015 04:02:08 +0200 Subject: [PATCH 14/33] refactor discovery: return entities instead of hashes --- lib/diaspora_federation/discovery/discovery.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/diaspora_federation/discovery/discovery.rb b/lib/diaspora_federation/discovery/discovery.rb index 82bbb92..079b6fd 100644 --- a/lib/diaspora_federation/discovery/discovery.rb +++ b/lib/diaspora_federation/discovery/discovery.rb @@ -21,7 +21,7 @@ module DiasporaFederation raise DiscoveryError, "Handle does not match: Wanted #{handle} but got #{clean_handle(webfinger.acct_uri)}" end - Entities::Person.new(person_hash) + person end private @@ -64,18 +64,18 @@ module DiasporaFederation @hcard ||= HCard.from_html get(webfinger.hcard_url) end - def person_hash - { + def person + Entities::Person.new( guid: hcard.guid || webfinger.guid, diaspora_handle: handle, url: webfinger.seed_url, exported_key: hcard.public_key || webfinger.public_key, - profile: Entities::Profile.new(profile_hash) - } + profile: profile + ) end - def profile_hash - { + def profile + Entities::Profile.new( diaspora_handle: handle, first_name: hcard.first_name, last_name: hcard.last_name, @@ -83,7 +83,7 @@ module DiasporaFederation image_url_medium: hcard.photo_medium_url, image_url_small: hcard.photo_small_url, searchable: hcard.searchable - } + ) end end end From ac8832ee4abd978159c0084925a844c11c97fe77 Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Tue, 21 Jul 2015 02:00:36 +0200 Subject: [PATCH 15/33] add diaspora handle validator and test --- lib/diaspora_federation/validators.rb | 3 +- .../validators/person_validator.rb | 2 +- .../validators/profile_validator.rb | 2 +- .../validators/rules/diaspora_id.rb | 43 +++++++++++ spec/factories.rb | 4 +- .../validators/rules/diaspora_id_spec.rb | 74 +++++++++++++++++++ 6 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 lib/diaspora_federation/validators/rules/diaspora_id.rb create mode 100644 spec/lib/diaspora_federation/validators/rules/diaspora_id_spec.rb diff --git a/lib/diaspora_federation/validators.rb b/lib/diaspora_federation/validators.rb index a5f0e86..79d6f40 100644 --- a/lib/diaspora_federation/validators.rb +++ b/lib/diaspora_federation/validators.rb @@ -1,6 +1,4 @@ require "validation" -require "validation/rule/not_empty" -require "validation/rule/email" require "validation/rule/regular_expression" require "validation/rule/uri" @@ -17,6 +15,7 @@ end require "diaspora_federation/validators/rules/birthday" require "diaspora_federation/validators/rules/boolean" +require "diaspora_federation/validators/rules/diaspora_id" require "diaspora_federation/validators/rules/guid" require "diaspora_federation/validators/rules/not_nil" require "diaspora_federation/validators/rules/public_key" diff --git a/lib/diaspora_federation/validators/person_validator.rb b/lib/diaspora_federation/validators/person_validator.rb index 1e58117..525a00c 100644 --- a/lib/diaspora_federation/validators/person_validator.rb +++ b/lib/diaspora_federation/validators/person_validator.rb @@ -5,7 +5,7 @@ module DiasporaFederation rule :guid, :guid - rule :diaspora_handle, %i(not_empty email) + rule :diaspora_handle, :diaspora_id rule :url, :u_r_i # WTF? :uri -> Uri -> "uninitialized constant Uri", :u_r_i -> URI -> \o/ diff --git a/lib/diaspora_federation/validators/profile_validator.rb b/lib/diaspora_federation/validators/profile_validator.rb index 1e6cfa8..ab672ff 100644 --- a/lib/diaspora_federation/validators/profile_validator.rb +++ b/lib/diaspora_federation/validators/profile_validator.rb @@ -3,7 +3,7 @@ module DiasporaFederation class ProfileValidator < Validation::Validator include Validation - rule :diaspora_handle, %i(not_empty email) + rule :diaspora_handle, :diaspora_id # the name must not contain a semicolon because of mentions # @{ ; } diff --git a/lib/diaspora_federation/validators/rules/diaspora_id.rb b/lib/diaspora_federation/validators/rules/diaspora_id.rb new file mode 100644 index 0000000..817e84d --- /dev/null +++ b/lib/diaspora_federation/validators/rules/diaspora_id.rb @@ -0,0 +1,43 @@ +module Validation + module Rule + # Diaspora ID validation rule + # + # This rule is based on https://github.com/zombor/Validator/blob/master/lib/validation/rule/email.rb + # which was adapted from https://github.com/emmanuel/aequitas/blob/master/lib/aequitas/rule/format/email_address.rb + class DiasporaId + DIASPORA_HANDLE = begin + letter = "a-zA-Z" + digit = "0-9" + username = "[#{letter}#{digit}\-\_\.]+" + atext = "[#{letter}#{digit}+\=\-\_]" + dot_atom = "#{atext}+([.]#{atext}*)*" + no_ws_ctl = '\x01-\x08\x11\x12\x14-\x1f\x7f' + text = '[\x01-\x09\x11\x12\x14-\x7f]' + quoted_pair = "(\\x5c#{text})" + dtext = "[#{no_ws_ctl}\\x21-\\x5a\\x5e-\\x7e]" + dcontent = "(?:#{dtext}|#{quoted_pair})" + domain_literal = "\\[#{dcontent}+\\]" + domain = "(?:#{dot_atom}|#{domain_literal})" + port = "(:[#{digit}]+)?" + addr_spec = "#{username}\@#{domain}#{port}" + + /\A#{addr_spec}\z/u + end + + # The error key for this rule + def error_key + :diaspora_id + end + + # Determines if value is a valid email + def valid_value?(value) + !DIASPORA_HANDLE.match(value).nil? + end + + # This rule has no params + def params + {} + end + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index 79f2b69..553a4fd 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -19,7 +19,7 @@ FactoryGirl.define do factory :person_entity, class: DiasporaFederation::Entities::Person do guid UUID.generate :compact - diaspora_handle "testing@example.com" + diaspora_handle url "http://localhost:3000/" exported_key { generate(:public_key) } profile { @@ -29,7 +29,7 @@ FactoryGirl.define do end factory :profile_entity, class: DiasporaFederation::Entities::Profile do - diaspora_handle "testing@example.com" + diaspora_handle first_name "my_name" last_name nil tag_string "#i #love #tags" diff --git a/spec/lib/diaspora_federation/validators/rules/diaspora_id_spec.rb b/spec/lib/diaspora_federation/validators/rules/diaspora_id_spec.rb new file mode 100644 index 0000000..f1f7a0e --- /dev/null +++ b/spec/lib/diaspora_federation/validators/rules/diaspora_id_spec.rb @@ -0,0 +1,74 @@ +describe Validation::Rule::DiasporaId do + it "will not accept parameters" do + validator = Validation::Validator.new({}) + expect { + validator.rule(:diaspora_id, diaspora_id: {param: true}) + }.to raise_error ArgumentError + end + + context "validation" do + it "validates a normal diaspora id" do + validator = Validation::Validator.new(OpenStruct.new(diaspora_id: "some_user@example.com")) + validator.rule(:diaspora_id, :diaspora_id) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "validates a diaspora id with localhost" do + validator = Validation::Validator.new(OpenStruct.new(diaspora_id: "some_user@localhost")) + validator.rule(:diaspora_id, :diaspora_id) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "validates a diaspora id with port" do + validator = Validation::Validator.new(OpenStruct.new(diaspora_id: "some_user@example.com:3000")) + validator.rule(:diaspora_id, :diaspora_id) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "validates a diaspora id with IPv4 address" do + validator = Validation::Validator.new(OpenStruct.new(diaspora_id: "some_user@123.45.67.89")) + validator.rule(:diaspora_id, :diaspora_id) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "validates a diaspora id with IPv6 address" do + validator = Validation::Validator.new(OpenStruct.new(diaspora_id: "some_user@[2001:1234:5678:90ab:cdef::1]")) + validator.rule(:diaspora_id, :diaspora_id) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "validates a diaspora id with . and -" do + validator = Validation::Validator.new(OpenStruct.new(diaspora_id: "some-fancy.user@example.com")) + validator.rule(:diaspora_id, :diaspora_id) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "fails if the diaspora id contains a / in the domain-name" do + validator = Validation::Validator.new(OpenStruct.new(diaspora_id: "some_user@example.com/friendica")) + validator.rule(:diaspora_id, :diaspora_id) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:diaspora_id) + end + + it "fails if the diaspora id contains a special-chars in the username" do + validator = Validation::Validator.new(OpenStruct.new(diaspora_id: "some_user$^%@example.com")) + validator.rule(:diaspora_id, :diaspora_id) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:diaspora_id) + end + end +end From 20d4646332667412677e248af1443389833b0f9a Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Tue, 21 Jul 2015 02:38:35 +0200 Subject: [PATCH 16/33] refactoring validation and write more tests --- .../validators/rules/birthday.rb | 12 +++---- .../validators/rules/boolean.rb | 12 +++---- .../validators/rules/guid.rb | 12 +++---- .../validators/rules/not_nil.rb | 12 +++---- .../validators/rules/public_key.rb | 12 +++---- .../validators/rules/tag_count.rb | 2 +- .../validators/person_validator_spec.rb | 28 +++++++++++++++ .../validators/profile_validator_spec.rb | 7 ++++ .../validators/rules/not_nil_spec.rb | 34 +++++++++++++++++++ .../validators/rules/tag_count_spec.rb | 9 +++++ 10 files changed, 104 insertions(+), 36 deletions(-) create mode 100644 spec/lib/diaspora_federation/validators/rules/not_nil_spec.rb diff --git a/lib/diaspora_federation/validators/rules/birthday.rb b/lib/diaspora_federation/validators/rules/birthday.rb index 3562922..89f97fe 100644 --- a/lib/diaspora_federation/validators/rules/birthday.rb +++ b/lib/diaspora_federation/validators/rules/birthday.rb @@ -3,13 +3,6 @@ require "date" module Validation module Rule class Birthday - attr_reader :params - - # no parameters - def initialize - @params = {} - end - def error_key :birthday end @@ -25,6 +18,11 @@ module Validation false end + + # This rule has no params + def params + {} + end end end end diff --git a/lib/diaspora_federation/validators/rules/boolean.rb b/lib/diaspora_federation/validators/rules/boolean.rb index f4498df..5ad166c 100644 --- a/lib/diaspora_federation/validators/rules/boolean.rb +++ b/lib/diaspora_federation/validators/rules/boolean.rb @@ -1,13 +1,6 @@ module Validation module Rule class Boolean - attr_reader :params - - # no parameters - def initialize - @params = {} - end - def error_key :numeric end @@ -25,6 +18,11 @@ module Validation false end end + + # This rule has no params + def params + {} + end end end end diff --git a/lib/diaspora_federation/validators/rules/guid.rb b/lib/diaspora_federation/validators/rules/guid.rb index 7aca5d8..f559fc6 100644 --- a/lib/diaspora_federation/validators/rules/guid.rb +++ b/lib/diaspora_federation/validators/rules/guid.rb @@ -1,13 +1,6 @@ module Validation module Rule class Guid - attr_reader :params - - # no parameters - def initialize - @params = {} - end - def error_key :guid end @@ -15,6 +8,11 @@ module Validation def valid_value?(value) value.is_a?(String) && value.downcase =~ /\A[0-9a-z\-_@.:]{16,}\z/ end + + # This rule has no params + def params + {} + end end end end diff --git a/lib/diaspora_federation/validators/rules/not_nil.rb b/lib/diaspora_federation/validators/rules/not_nil.rb index 834384f..d3866ae 100644 --- a/lib/diaspora_federation/validators/rules/not_nil.rb +++ b/lib/diaspora_federation/validators/rules/not_nil.rb @@ -1,13 +1,6 @@ module Validation module Rule class NotNil - attr_reader :params - - # no parameters - def initialize - @params = {} - end - def error_key :not_nil end @@ -15,6 +8,11 @@ module Validation def valid_value?(value) !value.nil? end + + # This rule has no params + def params + {} + end end end end diff --git a/lib/diaspora_federation/validators/rules/public_key.rb b/lib/diaspora_federation/validators/rules/public_key.rb index cc35e07..e7681ff 100644 --- a/lib/diaspora_federation/validators/rules/public_key.rb +++ b/lib/diaspora_federation/validators/rules/public_key.rb @@ -1,13 +1,6 @@ module Validation module Rule class PublicKey - attr_reader :params - - # no parameters - def initialize - @params = {} - end - def error_key :public_key end @@ -19,6 +12,11 @@ module Validation (value.strip.start_with?("-----BEGIN RSA PUBLIC KEY-----") && value.strip.end_with?("-----END RSA PUBLIC KEY-----")) end + + # This rule has no params + def params + {} + end end end end diff --git a/lib/diaspora_federation/validators/rules/tag_count.rb b/lib/diaspora_federation/validators/rules/tag_count.rb index 8f48082..e46fd7c 100644 --- a/lib/diaspora_federation/validators/rules/tag_count.rb +++ b/lib/diaspora_federation/validators/rules/tag_count.rb @@ -9,7 +9,7 @@ module Validation # @option params [Fixnum] :maximum maximum allowed tag count def initialize(params) unless params.include?(:maximum) && params[:maximum].is_a?(Fixnum) - raise "A number has to be specified for :maximum" + raise ArgumentError, "A number has to be specified for :maximum" end @params = params diff --git a/spec/lib/diaspora_federation/validators/person_validator_spec.rb b/spec/lib/diaspora_federation/validators/person_validator_spec.rb index f954d36..b4d462b 100644 --- a/spec/lib/diaspora_federation/validators/person_validator_spec.rb +++ b/spec/lib/diaspora_federation/validators/person_validator_spec.rb @@ -20,6 +20,34 @@ module DiasporaFederation let(:property) { :guid } end + context "#url" do + it "fails for url with special chars" do + instance = OpenStruct.new(FactoryGirl.attributes_for(:person_entity, url: "https://asdf$%.com")) + validator = Validators::PersonValidator.new(instance) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:url) + end + + it "fails for url without scheme" do + instance = OpenStruct.new(FactoryGirl.attributes_for(:person_entity, url: "example.com")) + validator = Validators::PersonValidator.new(instance) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:url) + end + end + + context "#profile" do + it "fails if profile is nil" do + instance = OpenStruct.new(FactoryGirl.attributes_for(:person_entity, profile: nil)) + validator = Validators::PersonValidator.new(instance) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:profile) + end + end + context "#exported_key" do it "fails for malformed rsa key" do instance = OpenStruct.new(FactoryGirl.attributes_for(:person_entity, exported_key: "ASDF")) diff --git a/spec/lib/diaspora_federation/validators/profile_validator_spec.rb b/spec/lib/diaspora_federation/validators/profile_validator_spec.rb index ef11474..2339eee 100644 --- a/spec/lib/diaspora_federation/validators/profile_validator_spec.rb +++ b/spec/lib/diaspora_federation/validators/profile_validator_spec.rb @@ -19,6 +19,13 @@ module DiasporaFederation %i(first_name last_name).each do |prop| describe "##{prop}" do + it "allowed to be nil" do + validator = Validators::ProfileValidator.new(profile_stub(prop => nil)) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + it "allowed to contain special chars" do validator = Validators::ProfileValidator.new(profile_stub(prop => "cool name ©")) diff --git a/spec/lib/diaspora_federation/validators/rules/not_nil_spec.rb b/spec/lib/diaspora_federation/validators/rules/not_nil_spec.rb new file mode 100644 index 0000000..67d5646 --- /dev/null +++ b/spec/lib/diaspora_federation/validators/rules/not_nil_spec.rb @@ -0,0 +1,34 @@ +describe Validation::Rule::NotNil do + it "will not accept parameters" do + validator = Validation::Validator.new({}) + expect { + validator.rule(:not_nil, not_nil: {param: true}) + }.to raise_error ArgumentError + end + + context "validation" do + it "validates a string " do + validator = Validation::Validator.new(OpenStruct.new(not_nil: "abcd")) + validator.rule(:not_nil, :not_nil) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "validates a object " do + validator = Validation::Validator.new(OpenStruct.new(not_nil: Object.new)) + validator.rule(:not_nil, :not_nil) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "fails if it is nil" do + validator = Validation::Validator.new(OpenStruct.new(not_nil: nil)) + validator.rule(:not_nil, :not_nil) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:not_nil) + end + end +end diff --git a/spec/lib/diaspora_federation/validators/rules/tag_count_spec.rb b/spec/lib/diaspora_federation/validators/rules/tag_count_spec.rb index 32c1aa5..c301b41 100644 --- a/spec/lib/diaspora_federation/validators/rules/tag_count_spec.rb +++ b/spec/lib/diaspora_federation/validators/rules/tag_count_spec.rb @@ -6,6 +6,15 @@ describe Validation::Rule::TagCount do }.to raise_error ArgumentError end + it "requires a integer as parameter" do + validator = Validation::Validator.new({}) + [nil, "", 5.5].each do |val| + expect { + validator.rule(:tags, tag_count: {maximum: val}) + }.to raise_error ArgumentError, "A number has to be specified for :maximum" + end + end + context "validation" do let(:tag_str) { "#i #love #tags" } From fe3c6b6161f0f7d619d83ecf8646ebff89268661 Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Wed, 22 Jul 2015 00:46:57 +0200 Subject: [PATCH 17/33] refactor HostMeta --- .../discovery/host_meta.rb | 23 +++++++++---------- .../discovery/host_meta_spec.rb | 5 ++++ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/diaspora_federation/discovery/host_meta.rb b/lib/diaspora_federation/discovery/host_meta.rb index fce8077..9346b6e 100644 --- a/lib/diaspora_federation/discovery/host_meta.rb +++ b/lib/diaspora_federation/discovery/host_meta.rb @@ -19,8 +19,13 @@ module DiasporaFederation class HostMeta private_class_method :new + # @param [String] webfinger_url the webfinger-url + def initialize(webfinger_url) + @webfinger_url = webfinger_url + end + # URL fragment to append to the base URL - WEBFINGER_SUFFIX = "webfinger?q={uri}" + WEBFINGER_SUFFIX = "/webfinger?q={uri}" # Returns the WebFinger URL that was used to build this instance (either from # xml or by giving a base URL). @@ -42,18 +47,14 @@ module DiasporaFederation # Builds a new HostMeta instance and constructs the WebFinger URL from the # given base URL by appending HostMeta::WEBFINGER_SUFFIX. + # @param [String, URL] base_url the base-url for the webfinger-url # @return [HostMeta] # @raise [InvalidData] if the webfinger url is malformed def self.from_base_url(base_url) - raise ArgumentError, "base_url is not a String" unless base_url.instance_of?(String) - - base_url += "/" unless base_url.end_with?("/") - webfinger_url = base_url + WEBFINGER_SUFFIX + webfinger_url = "#{base_url.to_s.chomp('/')}#{WEBFINGER_SUFFIX}" raise InvalidData, "invalid webfinger url: #{webfinger_url}" unless webfinger_url_valid?(webfinger_url) - hm = allocate - hm.instance_variable_set(:@webfinger_url, webfinger_url) - hm + new(webfinger_url) end # Reads the given Host Meta XML document string and populates the @@ -67,16 +68,14 @@ module DiasporaFederation webfinger_url = webfinger_url_from_xrd(data) raise InvalidData, "invalid webfinger url: #{webfinger_url}" unless webfinger_url_valid?(webfinger_url) - hm = allocate - hm.instance_variable_set(:@webfinger_url, webfinger_url) - hm + new(webfinger_url) end # Applies some basic sanity-checking to the given URL # @param [String] url validation subject # @return [Boolean] validation result def self.webfinger_url_valid?(url) - !url.nil? && url.instance_of?(String) && url =~ %r{^https?:\/\/.*\{uri\}}i + !url.nil? && url.instance_of?(String) && url =~ %r{^https?:\/\/.*\/.*\{uri\}.*}i end private_class_method :webfinger_url_valid? diff --git a/spec/lib/diaspora_federation/discovery/host_meta_spec.rb b/spec/lib/diaspora_federation/discovery/host_meta_spec.rb index 2f82ebf..66e7fc8 100644 --- a/spec/lib/diaspora_federation/discovery/host_meta_spec.rb +++ b/spec/lib/diaspora_federation/discovery/host_meta_spec.rb @@ -20,6 +20,11 @@ XML expect(hm.to_xml).to eq(xml) end + it "converts object to string" do + hm = Discovery::HostMeta.from_base_url(URI(base_url)) + expect(hm.to_xml).to eq(xml) + end + it "appends a '/' if necessary" do hm = Discovery::HostMeta.from_base_url("https://pod.example.tld") expect(hm.to_xml).to eq(xml) From 5d1f6ab220ebb5b2cd4926a3b2c90af7e5c6544b Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Wed, 22 Jul 2015 00:54:14 +0200 Subject: [PATCH 18/33] remove travis.sh --- .travis.yml | 2 +- script/ci/travis.sh | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 100755 script/ci/travis.sh diff --git a/.travis.yml b/.travis.yml index 5e55165..1e8e3a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,4 +16,4 @@ branches: before_install: gem install bundler bundler_args: "--deployment --without development --jobs=3 --retry=3" -script: "./script/ci/travis.sh" +script: bundle exec rake --trace diff --git a/script/ci/travis.sh b/script/ci/travis.sh deleted file mode 100755 index 39cb575..0000000 --- a/script/ci/travis.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -command="bundle exec rake --trace" -exec $command From 89de392fda60b1409bea87f44efd9bc8496b66d5 Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Wed, 22 Jul 2015 01:22:17 +0200 Subject: [PATCH 19/33] refactoring: use more tap :) --- lib/diaspora_federation/discovery/h_card.rb | 8 ++-- .../discovery/web_finger.rb | 8 ++-- .../discovery/xrd_document.rb | 19 ++++---- lib/diaspora_federation/entity.rb | 48 ++++++++----------- 4 files changed, 37 insertions(+), 46 deletions(-) diff --git a/lib/diaspora_federation/discovery/h_card.rb b/lib/diaspora_federation/discovery/h_card.rb index 5a247af..941b7b2 100644 --- a/lib/diaspora_federation/discovery/h_card.rb +++ b/lib/diaspora_federation/discovery/h_card.rb @@ -161,7 +161,7 @@ module DiasporaFederation def self.from_html(html_string) doc = parse_html_and_validate(html_string) - data = { + new( guid: guid_from_doc(doc), nickname: content_from_doc(doc, :nickname), full_name: content_from_doc(doc, :fn), @@ -173,12 +173,10 @@ module DiasporaFederation # TODO: public key is new and can be missing public_key: (content_from_doc(doc, :key) unless element_from_doc(doc, :key).nil?), - # TODO: remove me! ################### + # TODO: remove first_name and last_name! first_name: content_from_doc(doc, :given_name), last_name: content_from_doc(doc, :family_name) - ####################################### - } - new(data) + ) end private diff --git a/lib/diaspora_federation/discovery/web_finger.rb b/lib/diaspora_federation/discovery/web_finger.rb index a500d43..d8759a8 100644 --- a/lib/diaspora_federation/discovery/web_finger.rb +++ b/lib/diaspora_federation/discovery/web_finger.rb @@ -175,10 +175,10 @@ module DiasporaFederation # @return [Hash] data XML data # @raise [InvalidData] if the given XML string is invalid or incomplete def self.parse_xml_and_validate(webfinger_xml) - data = XrdDocument.xml_data(webfinger_xml) - valid = data.key?(:subject) && data.key?(:aliases) && data.key?(:links) - raise InvalidData, "webfinger xml is incomplete" unless valid - data + XrdDocument.xml_data(webfinger_xml).tap do |data| + valid = data.key?(:subject) && data.key?(:aliases) && data.key?(:links) + raise InvalidData, "webfinger xml is incomplete" unless valid + end end private_class_method :parse_xml_and_validate diff --git a/lib/diaspora_federation/discovery/xrd_document.rb b/lib/diaspora_federation/discovery/xrd_document.rb index c03f077..7e1a22a 100644 --- a/lib/diaspora_federation/discovery/xrd_document.rb +++ b/lib/diaspora_federation/discovery/xrd_document.rb @@ -92,19 +92,18 @@ module DiasporaFederation # @raise [InvalidDocument] if the XRD is malformed def self.xml_data(xrd_doc) doc = parse_xrd_document(xrd_doc) - data = {} - exp_elem = doc.at_xpath("xrd:XRD/xrd:Expires", NS) - data[:expires] = DateTime.strptime(exp_elem.content, DATETIME_FORMAT) unless exp_elem.nil? + {}.tap do |data| + exp_elem = doc.at_xpath("xrd:XRD/xrd:Expires", NS) + data[:expires] = DateTime.strptime(exp_elem.content, DATETIME_FORMAT) unless exp_elem.nil? - subj_elem = doc.at_xpath("xrd:XRD/xrd:Subject", NS) - data[:subject] = subj_elem.content unless subj_elem.nil? + subj_elem = doc.at_xpath("xrd:XRD/xrd:Subject", NS) + data[:subject] = subj_elem.content unless subj_elem.nil? - parse_aliases_from_xml_doc(doc, data) - parse_properties_from_xml_doc(doc, data) - parse_links_from_xml_doc(doc, data) - - data + parse_aliases_from_xml_doc(doc, data) + parse_properties_from_xml_doc(doc, data) + parse_links_from_xml_doc(doc, data) + end end private diff --git a/lib/diaspora_federation/entity.rb b/lib/diaspora_federation/entity.rb index 814d447..ffe6be0 100644 --- a/lib/diaspora_federation/entity.rb +++ b/lib/diaspora_federation/entity.rb @@ -76,16 +76,12 @@ module DiasporaFederation # some of this is from Rails "Inflector.demodulize" and "Inflector.undersore" def self.entity_name - word = name.dup - i = word.rindex("::") - word = word[(i + 2)..-1] if i - - word.gsub!("::", "/") - word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2') - word.gsub!(/([a-z\d])([A-Z])/, '\1_\2') - word.tr!("-", "_") - word.downcase! - word + name.rpartition("::").last.tap do |word| + word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2') + word.gsub!(/([a-z\d])([A-Z])/, '\1_\2') + word.tr!("-", "_") + word.downcase! + end end private @@ -122,30 +118,28 @@ module DiasporaFederation # @return [Nokogiri::XML::Element] root node def entity_xml doc = Nokogiri::XML::DocumentFragment.new(Nokogiri::XML::Document.new) - root_element = Nokogiri::XML::Element.new(self.class.entity_name, doc) - - self.class.class_props.each do |prop_def| - name = prop_def[:name] - type = prop_def[:type] - if type == String - root_element << simple_node(doc, name) - else - # call #to_xml for each item and append to root - [*send(name)].compact.each do |item| - root_element << item.to_xml + Nokogiri::XML::Element.new(self.class.entity_name, doc).tap do |root_element| + self.class.class_props.each do |prop_def| + name = prop_def[:name] + type = prop_def[:type] + if type == String + root_element << simple_node(doc, name) + else + # call #to_xml for each item and append to root + [*send(name)].compact.each do |item| + root_element << item.to_xml + end end end end - - root_element end # create simple node, fill it with text and append to root def simple_node(doc, name) - node = Nokogiri::XML::Element.new(name.to_s, doc) - data = send(name).to_s - node.content = data unless data.empty? - node + Nokogiri::XML::Element.new(name.to_s, doc).tap do |node| + data = send(name).to_s + node.content = data unless data.empty? + end end end end From 66b63f553534fb84b2631249a524c03a3ccf5c06 Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Wed, 22 Jul 2015 01:26:01 +0200 Subject: [PATCH 20/33] fix URI validator --- lib/diaspora_federation/validators/person_validator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/diaspora_federation/validators/person_validator.rb b/lib/diaspora_federation/validators/person_validator.rb index 525a00c..1ef5210 100644 --- a/lib/diaspora_federation/validators/person_validator.rb +++ b/lib/diaspora_federation/validators/person_validator.rb @@ -7,7 +7,7 @@ module DiasporaFederation rule :diaspora_handle, :diaspora_id - rule :url, :u_r_i # WTF? :uri -> Uri -> "uninitialized constant Uri", :u_r_i -> URI -> \o/ + rule :url, :URI rule :profile, :not_nil From da81c2e587669045c2e1bf83fe1aa0dd2f968cfa Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Wed, 22 Jul 2015 01:28:08 +0200 Subject: [PATCH 21/33] refactor boolean validator rule --- lib/diaspora_federation/validators/rules/boolean.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/diaspora_federation/validators/rules/boolean.rb b/lib/diaspora_federation/validators/rules/boolean.rb index 5ad166c..64eb9f9 100644 --- a/lib/diaspora_federation/validators/rules/boolean.rb +++ b/lib/diaspora_federation/validators/rules/boolean.rb @@ -12,7 +12,7 @@ module Validation true if value =~ /\A(true|false|t|f|yes|no|y|n|1|0)\z/i elsif value.is_a?(Fixnum) true if value == 1 || value == 0 - elsif value.is_a?(TrueClass) || value.is_a?(FalseClass) + elsif [true, false].include? value true else false From 03c94bc344e02f6ad1acf7722a71d97dd036153f Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Wed, 22 Jul 2015 01:30:45 +0200 Subject: [PATCH 22/33] refactor controller when person is not found --- .../diaspora_federation/h_card_controller.rb | 10 ++++++---- .../diaspora_federation/webfinger_controller.rb | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/controllers/diaspora_federation/h_card_controller.rb b/app/controllers/diaspora_federation/h_card_controller.rb index 70e8821..65e700d 100644 --- a/app/controllers/diaspora_federation/h_card_controller.rb +++ b/app/controllers/diaspora_federation/h_card_controller.rb @@ -9,10 +9,12 @@ module DiasporaFederation def hcard person_hcard = DiasporaFederation.callbacks.trigger(:person_hcard_fetch, params[:guid]) - return render nothing: true, status: 404 if person_hcard.nil? - - logger.info "hcard profile request for: #{person_hcard.nickname}:#{person_hcard.guid}" - render html: person_hcard.to_html.html_safe + if person_hcard.nil? + render nothing: true, status: 404 if person_hcard.nil? + else + logger.info "hcard profile request for: #{person_hcard.nickname}:#{person_hcard.guid}" + render html: person_hcard.to_html.html_safe + end end end end diff --git a/app/controllers/diaspora_federation/webfinger_controller.rb b/app/controllers/diaspora_federation/webfinger_controller.rb index 22bc63a..ac9fd5a 100644 --- a/app/controllers/diaspora_federation/webfinger_controller.rb +++ b/app/controllers/diaspora_federation/webfinger_controller.rb @@ -37,10 +37,12 @@ module DiasporaFederation def legacy_webfinger person_wf = find_person_webfinger(params[:q]) if params[:q] - return render nothing: true, status: 404 if person_wf.nil? - - logger.info "webfinger profile request for: #{person_wf.acct_uri}" - render body: person_wf.to_xml, content_type: "application/xrd+xml" + if person_wf.nil? + render nothing: true, status: 404 + else + logger.info "webfinger profile request for: #{person_wf.acct_uri}" + render body: person_wf.to_xml, content_type: "application/xrd+xml" + end end private From 0deb74c10330269b89815ed8aa34774c6a33a05f Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Thu, 23 Jul 2015 01:22:23 +0200 Subject: [PATCH 23/33] refactor: rename diaspora_handle to diaspora_id diaspora uses the new wording "diaspora ID" instead of "diaspora handle" --- README.md | 4 +- .../discovery/discovery.rb | 45 ++++++++++--------- lib/diaspora_federation/discovery/h_card.rb | 2 +- lib/diaspora_federation/entities/person.rb | 9 ++-- lib/diaspora_federation/entities/profile.rb | 11 ++--- .../validators/person_validator.rb | 2 +- .../validators/profile_validator.rb | 4 +- .../validators/rules/diaspora_id.rb | 4 +- .../webfinger_controller_spec.rb | 2 +- spec/factories.rb | 10 ++--- .../discovery/discovery_spec.rb | 24 +++++----- .../discovery/web_finger_spec.rb | 4 +- .../validators/person_validator_spec.rb | 4 +- .../validators/profile_validator_spec.rb | 4 +- spec/spec_helper.rb | 2 +- spec/support/fixture_builder.rb | 2 +- spec/support/shared_validator_specs.rb | 8 ++-- test/dummy/app/models/person.rb | 2 +- .../initializers/diaspora_federation.rb | 6 +-- ...1_rename_diaspora_handle_to_diaspora_id.rb | 5 +++ test/dummy/db/schema.rb | 4 +- 21 files changed, 83 insertions(+), 75 deletions(-) create mode 100644 test/dummy/db/migrate/20150722224751_rename_diaspora_handle_to_diaspora_id.rb diff --git a/README.md b/README.md index 4faf7e0..73b5f5b 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,8 @@ DiasporaFederation.configure do |config| config.server_uri = AppConfig.pod_uri config.define_callbacks do - on :person_webfinger_fetch do |handle| - person = Person.find_local_by_diaspora_handle(handle) + on :person_webfinger_fetch do |diaspora_id| + person = Person.find_local_by_diaspora_id(diaspora_id) if person DiasporaFederation::Discovery::WebFinger.new( # ... diff --git a/lib/diaspora_federation/discovery/discovery.rb b/lib/diaspora_federation/discovery/discovery.rb index 079b6fd..5ecb1d6 100644 --- a/lib/diaspora_federation/discovery/discovery.rb +++ b/lib/diaspora_federation/discovery/discovery.rb @@ -1,24 +1,25 @@ module DiasporaFederation module Discovery - # This class contains the logic to fetch all data for the given handle + # This class contains the logic to fetch all data for the given diaspora ID class Discovery include DiasporaFederation::Logging - # @return [String] the handle of the account - attr_reader :handle + # @return [String] the diaspora ID of the account + attr_reader :diaspora_id - # @param [String] account the diaspora handle to discover - def initialize(account) - @handle = clean_handle(account) + # @param [String] diaspora_id the diaspora id to discover + def initialize(diaspora_id) + @diaspora_id = clean_diaspora_id(diaspora_id) end # fetch all metadata for the account # @return [Person] def fetch - logger.info "Fetch data for #{handle}" + logger.info "Fetch data for #{diaspora_id}" - unless handle == clean_handle(webfinger.acct_uri) - raise DiscoveryError, "Handle does not match: Wanted #{handle} but got #{clean_handle(webfinger.acct_uri)}" + unless diaspora_id == clean_diaspora_id(webfinger.acct_uri) + raise DiscoveryError, "Diaspora ID does not match: Wanted #{diaspora_id} but got" \ + " #{clean_diaspora_id(webfinger.acct_uri)}" end person @@ -26,34 +27,34 @@ module DiasporaFederation private - def clean_handle(account) - account.strip.sub("acct:", "").to_s.downcase + def clean_diaspora_id(diaspora_id) + diaspora_id.strip.sub("acct:", "").to_s.downcase end def get(url, http_fallback=false) - logger.info "Fetching #{url} for #{handle}" + logger.info "Fetching #{url} for #{diaspora_id}" response = Fetcher.get(url) raise "Failed to fetch #{url}: #{response.status}" unless response.success? response.body rescue => e if http_fallback && url.start_with?("https://") - logger.warn "Retry with http: #{url} for #{handle}: #{e.class}: #{e.message}" + logger.warn "Retry with http: #{url} for #{diaspora_id}: #{e.class}: #{e.message}" url.sub!("https://", "http://") retry else - raise DiscoveryError, "Failed to fetch #{url} for #{handle}: #{e.class}: #{e.message}" + raise DiscoveryError, "Failed to fetch #{url} for #{diaspora_id}: #{e.class}: #{e.message}" end end def host_meta_url - domain = handle.split("@")[1] + domain = diaspora_id.split("@")[1] "https://#{domain}/.well-known/host-meta" end def legacy_webfinger_url_from_host_meta # this tries the xrd url with https first, then falls back to http host_meta = HostMeta.from_xml get(host_meta_url, true) - host_meta.webfinger_template_url.gsub("{uri}", "acct:#{handle}") + host_meta.webfinger_template_url.gsub("{uri}", "acct:#{diaspora_id}") end def webfinger @@ -66,17 +67,17 @@ module DiasporaFederation def person Entities::Person.new( - guid: hcard.guid || webfinger.guid, - diaspora_handle: handle, - url: webfinger.seed_url, - exported_key: hcard.public_key || webfinger.public_key, - profile: profile + guid: hcard.guid || webfinger.guid, + diaspora_id: diaspora_id, + url: webfinger.seed_url, + exported_key: hcard.public_key || webfinger.public_key, + profile: profile ) end def profile Entities::Profile.new( - diaspora_handle: handle, + diaspora_id: diaspora_id, first_name: hcard.first_name, last_name: hcard.last_name, image_url: hcard.photo_large_url, diff --git a/lib/diaspora_federation/discovery/h_card.rb b/lib/diaspora_federation/discovery/h_card.rb index 941b7b2..e9222f5 100644 --- a/lib/diaspora_federation/discovery/h_card.rb +++ b/lib/diaspora_federation/discovery/h_card.rb @@ -48,7 +48,7 @@ module DiasporaFederation property :guid # @!attribute [r] nickname - # the first part of the diaspora handle + # the first part of the diaspora ID # @return [String] nickname property :nickname diff --git a/lib/diaspora_federation/entities/person.rb b/lib/diaspora_federation/entities/person.rb index 4dd2f09..a368369 100644 --- a/lib/diaspora_federation/entities/person.rb +++ b/lib/diaspora_federation/entities/person.rb @@ -7,10 +7,11 @@ module DiasporaFederation # @return [String] guid property :guid - # @!attribute [r] diaspora_handle - # The diaspora handle of the person - # @return [String] diaspora handle - property :diaspora_handle + # @!attribute [r] diaspora_id + # @todo refactoring with properties_dsl, xml name should be diaspora_handle + # The diaspora ID of the person + # @return [String] diaspora ID + property :diaspora_id # @!attribute [r] url # @see WebFinger#seed_url diff --git a/lib/diaspora_federation/entities/profile.rb b/lib/diaspora_federation/entities/profile.rb index 4c89671..6a338f0 100644 --- a/lib/diaspora_federation/entities/profile.rb +++ b/lib/diaspora_federation/entities/profile.rb @@ -2,11 +2,12 @@ module DiasporaFederation module Entities # this entity contains all the profile data of a person class Profile < Entity - # @!attribute [r] diaspora_handle - # The diaspora handle of the person - # @see Person#diaspora_handle - # @return [String] diaspora handle - property :diaspora_handle + # @!attribute [r] diaspora_id + # @todo refactoring with properties_dsl, xml name should be diaspora_handle + # The diaspora ID of the person + # @see Person#diaspora_id + # @return [String] diaspora ID + property :diaspora_id # @!attribute [r] first_name # @deprecated diff --git a/lib/diaspora_federation/validators/person_validator.rb b/lib/diaspora_federation/validators/person_validator.rb index 1ef5210..33c9957 100644 --- a/lib/diaspora_federation/validators/person_validator.rb +++ b/lib/diaspora_federation/validators/person_validator.rb @@ -5,7 +5,7 @@ module DiasporaFederation rule :guid, :guid - rule :diaspora_handle, :diaspora_id + rule :diaspora_id, :diaspora_id rule :url, :URI diff --git a/lib/diaspora_federation/validators/profile_validator.rb b/lib/diaspora_federation/validators/profile_validator.rb index ab672ff..4795f5a 100644 --- a/lib/diaspora_federation/validators/profile_validator.rb +++ b/lib/diaspora_federation/validators/profile_validator.rb @@ -3,10 +3,10 @@ module DiasporaFederation class ProfileValidator < Validation::Validator include Validation - rule :diaspora_handle, :diaspora_id + rule :diaspora_id, :diaspora_id # the name must not contain a semicolon because of mentions - # @{ ; } + # @{ ; } rule :first_name, regular_expression: {regex: /\A[^;]{,32}\z/} rule :last_name, regular_expression: {regex: /\A[^;]{,32}\z/} diff --git a/lib/diaspora_federation/validators/rules/diaspora_id.rb b/lib/diaspora_federation/validators/rules/diaspora_id.rb index 817e84d..f17f7b7 100644 --- a/lib/diaspora_federation/validators/rules/diaspora_id.rb +++ b/lib/diaspora_federation/validators/rules/diaspora_id.rb @@ -5,7 +5,7 @@ module Validation # This rule is based on https://github.com/zombor/Validator/blob/master/lib/validation/rule/email.rb # which was adapted from https://github.com/emmanuel/aequitas/blob/master/lib/aequitas/rule/format/email_address.rb class DiasporaId - DIASPORA_HANDLE = begin + DIASPORA_ID = begin letter = "a-zA-Z" digit = "0-9" username = "[#{letter}#{digit}\-\_\.]+" @@ -31,7 +31,7 @@ module Validation # Determines if value is a valid email def valid_value?(value) - !DIASPORA_HANDLE.match(value).nil? + !DIASPORA_ID.match(value).nil? end # This rule has no params diff --git a/spec/controllers/diaspora_federation/webfinger_controller_spec.rb b/spec/controllers/diaspora_federation/webfinger_controller_spec.rb index 3a7c87e..98d5949 100644 --- a/spec/controllers/diaspora_federation/webfinger_controller_spec.rb +++ b/spec/controllers/diaspora_federation/webfinger_controller_spec.rb @@ -48,7 +48,7 @@ module DiasporaFederation expect(response).to be_success end - it "contains the diaspora handle" do + it "contains the diaspora id" do get :legacy_webfinger, "q" => "acct:alice@localhost:3000" expect(response.body).to include "acct:alice@localhost:3000" end diff --git a/spec/factories.rb b/spec/factories.rb index 553a4fd..e46254a 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -5,11 +5,11 @@ def r_str end FactoryGirl.define do - sequence(:diaspora_handle) {|n| "person-#{n}-#{r_str}@localhost:3000" } + sequence(:diaspora_id) {|n| "person-#{n}-#{r_str}@localhost:3000" } sequence(:public_key) { OpenSSL::PKey::RSA.generate(1024).public_key.export } factory :person do - diaspora_handle + diaspora_id url "http://localhost:3000/" serialized_public_key { generate(:public_key) } after(:create) do |u| @@ -19,17 +19,17 @@ FactoryGirl.define do factory :person_entity, class: DiasporaFederation::Entities::Person do guid UUID.generate :compact - diaspora_handle + diaspora_id url "http://localhost:3000/" exported_key { generate(:public_key) } profile { DiasporaFederation::Entities::Profile.new( - FactoryGirl.attributes_for(:profile_entity, diaspora_handle: diaspora_handle)) + FactoryGirl.attributes_for(:profile_entity, diaspora_id: diaspora_id)) } end factory :profile_entity, class: DiasporaFederation::Entities::Profile do - diaspora_handle + diaspora_id first_name "my_name" last_name nil tag_string "#i #love #tags" diff --git a/spec/lib/diaspora_federation/discovery/discovery_spec.rb b/spec/lib/diaspora_federation/discovery/discovery_spec.rb index f209495..e70c933 100644 --- a/spec/lib/diaspora_federation/discovery/discovery_spec.rb +++ b/spec/lib/diaspora_federation/discovery/discovery_spec.rb @@ -3,18 +3,18 @@ module DiasporaFederation let(:host_meta_xrd) { FixtureGeneration.load_fixture("host-meta") } let(:webfinger_xrd) { FixtureGeneration.load_fixture("legacy-webfinger") } let(:hcard_html) { FixtureGeneration.load_fixture("hcard") } - let(:account) { alice.diaspora_handle } + let(:account) { alice.diaspora_id } let(:default_image) { "http://localhost:3000/assets/user/default.png" } describe "#intialize" do - it "sets handle" do + it "sets diaspora id" do discovery = Discovery::Discovery.new("some_user@example.com") - expect(discovery.handle).to eq("some_user@example.com") + expect(discovery.diaspora_id).to eq("some_user@example.com") end it "downcases account and strips whitespace, and sub 'acct:'" do discovery = Discovery::Discovery.new("acct:BIGBOY@Example.Com ") - expect(discovery.handle).to eq("bigboy@example.com") + expect(discovery.diaspora_id).to eq("bigboy@example.com") end end @@ -30,13 +30,13 @@ module DiasporaFederation person = Discovery::Discovery.new(account).fetch expect(person.guid).to eq(alice.guid) - expect(person.diaspora_handle).to eq(account) + expect(person.diaspora_id).to eq(account) expect(person.url).to eq(alice.url) expect(person.exported_key).to eq(alice.serialized_public_key) profile = person.profile - expect(profile.diaspora_handle).to eq(alice.diaspora_handle) + expect(profile.diaspora_id).to eq(alice.diaspora_id) expect(profile.first_name).to eq("Dummy") expect(profile.last_name).to eq("User") @@ -58,7 +58,7 @@ module DiasporaFederation person = Discovery::Discovery.new(account).fetch expect(person.guid).to eq(alice.guid) - expect(person.diaspora_handle).to eq(account) + expect(person.diaspora_id).to eq(account) end it "falls back to http if https fails with ssl error" do @@ -74,10 +74,10 @@ module DiasporaFederation person = Discovery::Discovery.new(account).fetch expect(person.guid).to eq(alice.guid) - expect(person.diaspora_handle).to eq(account) + expect(person.diaspora_id).to eq(account) end - it "fails if the handle does not match" do + it "fails if the diaspora id does not match" do modified_webfinger = webfinger_xrd.gsub(account, "anonther_user@example.com") stub_request(:get, "https://localhost:3000/.well-known/host-meta") @@ -88,7 +88,7 @@ module DiasporaFederation expect { Discovery::Discovery.new(account).fetch }.to raise_error Discovery::DiscoveryError end - it "fails if the handle was not found" do + it "fails if the diaspora id was not found" do stub_request(:get, "https://localhost:3000/.well-known/host-meta") .to_return(status: 200, body: host_meta_xrd) stub_request(:get, "http://localhost:3000/webfinger?q=acct:#{account}") @@ -173,13 +173,13 @@ module DiasporaFederation person = Discovery::Discovery.new(account).fetch expect(person.guid).to eq(alice.guid) - expect(person.diaspora_handle).to eq(account) + expect(person.diaspora_id).to eq(account) expect(person.url).to eq(alice.url) expect(person.exported_key).to eq(alice.serialized_public_key) profile = person.profile - expect(profile.diaspora_handle).to eq(alice.diaspora_handle) + expect(profile.diaspora_id).to eq(alice.diaspora_id) expect(profile.first_name).to be_nil expect(profile.last_name).to be_nil diff --git a/spec/lib/diaspora_federation/discovery/web_finger_spec.rb b/spec/lib/diaspora_federation/discovery/web_finger_spec.rb index a2d24a7..dbc372f 100644 --- a/spec/lib/diaspora_federation/discovery/web_finger_spec.rb +++ b/spec/lib/diaspora_federation/discovery/web_finger_spec.rb @@ -1,7 +1,7 @@ module DiasporaFederation describe Discovery::WebFinger do let(:person) { FactoryGirl.create(:person) } - let(:acct) { "acct:#{person.diaspora_handle}" } + let(:acct) { "acct:#{person.diaspora_id}" } let(:public_key_base64) { Base64.strict_encode64(person.serialized_public_key) } let(:xml) { @@ -28,7 +28,7 @@ XML context "generation" do it "creates a nice XML document" do wf = Discovery::WebFinger.new( - acct_uri: "acct:#{person.diaspora_handle}", + acct_uri: "acct:#{person.diaspora_id}", alias_url: person.alias_url, hcard_url: person.hcard_url, seed_url: person.url, diff --git a/spec/lib/diaspora_federation/validators/person_validator_spec.rb b/spec/lib/diaspora_federation/validators/person_validator_spec.rb index b4d462b..4240f94 100644 --- a/spec/lib/diaspora_federation/validators/person_validator_spec.rb +++ b/spec/lib/diaspora_federation/validators/person_validator_spec.rb @@ -8,10 +8,10 @@ module DiasporaFederation expect(validator.errors).to be_empty end - it_behaves_like "a diaspora_handle validator" do + it_behaves_like "a diaspora_id validator" do let(:entity) { :person_entity } let(:validator_class) { Validators::PersonValidator } - let(:property) { :diaspora_handle } + let(:property) { :diaspora_id } end it_behaves_like "a guid validator" do diff --git a/spec/lib/diaspora_federation/validators/profile_validator_spec.rb b/spec/lib/diaspora_federation/validators/profile_validator_spec.rb index 2339eee..01fc765 100644 --- a/spec/lib/diaspora_federation/validators/profile_validator_spec.rb +++ b/spec/lib/diaspora_federation/validators/profile_validator_spec.rb @@ -11,10 +11,10 @@ module DiasporaFederation expect(validator.errors).to be_empty end - it_behaves_like "a diaspora_handle validator" do + it_behaves_like "a diaspora_id validator" do let(:entity) { :profile_entity } let(:validator_class) { Validators::ProfileValidator } - let(:property) { :diaspora_handle } + let(:property) { :diaspora_id } end %i(first_name last_name).each do |prop| diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index be9589d..ceb42ca 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -30,7 +30,7 @@ require "entities" # some helper methods def alice - @alice ||= Person.find_by(diaspora_handle: "alice@localhost:3000") + @alice ||= Person.find_by(diaspora_id: "alice@localhost:3000") end # Requires supporting files with custom matchers and macros, etc, diff --git a/spec/support/fixture_builder.rb b/spec/support/fixture_builder.rb index dbb7f41..a737a58 100644 --- a/spec/support/fixture_builder.rb +++ b/spec/support/fixture_builder.rb @@ -11,6 +11,6 @@ FixtureBuilder.configure do |fbuilder| # now declare objects fbuilder.factory do - FactoryGirl.create(:person, diaspora_handle: "alice@localhost:3000") + FactoryGirl.create(:person, diaspora_id: "alice@localhost:3000") end end diff --git a/spec/support/shared_validator_specs.rb b/spec/support/shared_validator_specs.rb index 7601335..07b7845 100644 --- a/spec/support/shared_validator_specs.rb +++ b/spec/support/shared_validator_specs.rb @@ -4,8 +4,8 @@ def entity_stub(entity, property, val=nil) instance end -shared_examples "a diaspora_handle validator" do - it "validates a well-formed diaspora_handle" do +shared_examples "a diaspora_id validator" do + it "validates a well-formed diaspora_id" do validator = validator_class.new(entity_stub(entity, property)) expect(validator).to be_valid @@ -19,8 +19,8 @@ shared_examples "a diaspora_handle validator" do expect(validator.errors).to include(property) end - it "must resemble an email address" do - validator = validator_class.new(entity_stub(entity, property, "i am a weird handle @@@ ### 12345")) + it "must be a valid diaspora id" do + validator = validator_class.new(entity_stub(entity, property, "i am a weird diaspora id @@@ ### 12345")) expect(validator).not_to be_valid expect(validator.errors).to include(property) diff --git a/test/dummy/app/models/person.rb b/test/dummy/app/models/person.rb index a46a163..4fce4cd 100644 --- a/test/dummy/app/models/person.rb +++ b/test/dummy/app/models/person.rb @@ -7,7 +7,7 @@ class Person < ActiveRecord::Base def atom_url; "#{url}public/#{nickname}.atom" end def salmon_url; "#{url}receive/users/#{guid}" end - def nickname; diaspora_handle.split("@")[0] end + def nickname; diaspora_id.split("@")[0] end def photo_default_url; "#{url}assets/user/default.png" end diff --git a/test/dummy/config/initializers/diaspora_federation.rb b/test/dummy/config/initializers/diaspora_federation.rb index 190a1ae..d7883f1 100644 --- a/test/dummy/config/initializers/diaspora_federation.rb +++ b/test/dummy/config/initializers/diaspora_federation.rb @@ -16,11 +16,11 @@ DiasporaFederation.configure do |config| config.certificate_authorities = ca_file config.define_callbacks do - on :person_webfinger_fetch do |handle| - person = Person.find_by(diaspora_handle: handle) + on :person_webfinger_fetch do |diaspora_id| + person = Person.find_by(diaspora_id: diaspora_id) if person DiasporaFederation::Discovery::WebFinger.new( - acct_uri: "acct:#{person.diaspora_handle}", + acct_uri: "acct:#{person.diaspora_id}", alias_url: person.alias_url, hcard_url: person.hcard_url, seed_url: person.url, diff --git a/test/dummy/db/migrate/20150722224751_rename_diaspora_handle_to_diaspora_id.rb b/test/dummy/db/migrate/20150722224751_rename_diaspora_handle_to_diaspora_id.rb new file mode 100644 index 0000000..e997558 --- /dev/null +++ b/test/dummy/db/migrate/20150722224751_rename_diaspora_handle_to_diaspora_id.rb @@ -0,0 +1,5 @@ +class RenameDiasporaHandleToDiasporaId < ActiveRecord::Migration + def change + rename_column :people, :diaspora_handle, :diaspora_id + end +end diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index 7167574..dfcda3a 100644 --- a/test/dummy/db/schema.rb +++ b/test/dummy/db/schema.rb @@ -11,12 +11,12 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150614014411) do +ActiveRecord::Schema.define(version: 20150722224751) do create_table "people", force: :cascade do |t| t.string "guid", null: false t.text "url", null: false - t.string "diaspora_handle", null: false + t.string "diaspora_id", null: false t.text "serialized_public_key", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false From 71b1d6dc1ec07a3e948f34a89287b8faa8470641 Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Fri, 24 Jul 2015 02:05:02 +0200 Subject: [PATCH 24/33] add xml_name option to properties_dsl also: * only allow symbols as name and xml_name * use public_send instead of send --- lib/diaspora_federation/entities/person.rb | 3 +- lib/diaspora_federation/entities/profile.rb | 3 +- lib/diaspora_federation/entity.rb | 31 +++++++++------- lib/diaspora_federation/properties_dsl.rb | 13 +++++-- spec/entities.rb | 5 +++ spec/lib/diaspora_federation/entity_spec.rb | 23 +++++++++++- .../properties_dsl_spec.rb | 35 ++++++++++++++----- 7 files changed, 84 insertions(+), 29 deletions(-) diff --git a/lib/diaspora_federation/entities/person.rb b/lib/diaspora_federation/entities/person.rb index a368369..ebbcbcc 100644 --- a/lib/diaspora_federation/entities/person.rb +++ b/lib/diaspora_federation/entities/person.rb @@ -8,10 +8,9 @@ module DiasporaFederation property :guid # @!attribute [r] diaspora_id - # @todo refactoring with properties_dsl, xml name should be diaspora_handle # The diaspora ID of the person # @return [String] diaspora ID - property :diaspora_id + property :diaspora_id, xml_name: :diaspora_handle # @!attribute [r] url # @see WebFinger#seed_url diff --git a/lib/diaspora_federation/entities/profile.rb b/lib/diaspora_federation/entities/profile.rb index 6a338f0..c819cff 100644 --- a/lib/diaspora_federation/entities/profile.rb +++ b/lib/diaspora_federation/entities/profile.rb @@ -3,11 +3,10 @@ module DiasporaFederation # this entity contains all the profile data of a person class Profile < Entity # @!attribute [r] diaspora_id - # @todo refactoring with properties_dsl, xml name should be diaspora_handle # The diaspora ID of the person # @see Person#diaspora_id # @return [String] diaspora ID - property :diaspora_id + property :diaspora_id, xml_name: :diaspora_handle # @!attribute [r] first_name # @deprecated diff --git a/lib/diaspora_federation/entity.rb b/lib/diaspora_federation/entity.rb index ffe6be0..cbcda87 100644 --- a/lib/diaspora_federation/entity.rb +++ b/lib/diaspora_federation/entity.rb @@ -15,6 +15,7 @@ module DiasporaFederation # property :prop # property :optional, default: false # property :dynamic_default, default: -> { Time.now } + # property :another_prop, xml_name: :another_name # entity :nested, NestedEntity # entity :multiple, [OtherEntity] # end @@ -59,7 +60,7 @@ module DiasporaFederation # @return [Hash] entity data (mostly equal to the hash used for initialization). def to_h self.class.class_prop_names.each_with_object({}) do |prop, hash| - hash[prop] = send(prop) + hash[prop] = public_send(prop) end end @@ -120,24 +121,28 @@ module DiasporaFederation doc = Nokogiri::XML::DocumentFragment.new(Nokogiri::XML::Document.new) Nokogiri::XML::Element.new(self.class.entity_name, doc).tap do |root_element| self.class.class_props.each do |prop_def| - name = prop_def[:name] - type = prop_def[:type] - if type == String - root_element << simple_node(doc, name) - else - # call #to_xml for each item and append to root - [*send(name)].compact.each do |item| - root_element << item.to_xml - end - end + add_property_to_xml(doc, prop_def, root_element) + end + end + end + + def add_property_to_xml(doc, prop_def, root_element) + property = prop_def[:name] + type = prop_def[:type] + if type == String + root_element << simple_node(doc, prop_def[:xml_name], property) + else + # call #to_xml for each item and append to root + [*public_send(property)].compact.each do |item| + root_element << item.to_xml end end end # create simple node, fill it with text and append to root - def simple_node(doc, name) + def simple_node(doc, name, property) Nokogiri::XML::Element.new(name.to_s, doc).tap do |node| - data = send(name).to_s + data = public_send(property).to_s node.content = data unless data.empty? end end diff --git a/lib/diaspora_federation/properties_dsl.rb b/lib/diaspora_federation/properties_dsl.rb index abfb9fb..12525d7 100644 --- a/lib/diaspora_federation/properties_dsl.rb +++ b/lib/diaspora_federation/properties_dsl.rb @@ -6,6 +6,7 @@ module DiasporaFederation # property :prop # property :optional, default: false # property :dynamic_default, default: -> { Time.now } + # property :another_prop, xml_name: :another_name # entity :nested, NestedEntity # entity :multiple, [OtherEntity] module PropertiesDSL @@ -19,6 +20,7 @@ module DiasporaFederation # @param [Hash] opts further options # @option opts [Object, #call] :default a default value, making the # property optional + # @option opts [Symbol] :xml_name another name used for xml generation def property(name, opts={}) define_property name, String, opts end @@ -69,7 +71,14 @@ module DiasporaFederation def define_property(name, type, opts={}) raise InvalidName unless name_valid?(name) - class_props << {name: name, type: type} + xml_name = name + if opts.has_key? :xml_name + raise ArgumentError, "xml_name is not supported for nested entities" unless type == String + xml_name = opts[:xml_name] + raise InvalidName, "invalid xml_name" unless name_valid?(xml_name) + end + + class_props << {name: name, xml_name: xml_name, type: type} default_props[name] = opts[:default] if opts.has_key? :default instance_eval { attr_reader name } @@ -79,7 +88,7 @@ module DiasporaFederation # @param [String, Symbol] name the name to check # @return [Boolean] def name_valid?(name) - name.instance_of?(Symbol) || name.instance_of?(String) + name.instance_of?(Symbol) end # checks if the type extends {Entity} diff --git a/spec/entities.rb b/spec/entities.rb index ee8f312..8ec2002 100644 --- a/spec/entities.rb +++ b/spec/entities.rb @@ -20,5 +20,10 @@ module DiasporaFederation entity :test, TestEntity entity :multi, [OtherEntity] end + + class TestEntityWithXmlName < DiasporaFederation::Entity + property :test + property :qwer, xml_name: :asdf + end end end diff --git a/spec/lib/diaspora_federation/entity_spec.rb b/spec/lib/diaspora_federation/entity_spec.rb index 408ebed..bc168d3 100644 --- a/spec/lib/diaspora_federation/entity_spec.rb +++ b/spec/lib/diaspora_federation/entity_spec.rb @@ -61,7 +61,9 @@ module DiasporaFederation it "contains nodes for each of the properties" do entity = Entities::TestDefaultEntity.new(data) - entity.to_xml.children.each do |node| + xml_children = entity.to_xml.children + expect(xml_children).to have_exactly(4).items + xml_children.each do |node| expect(%w(test1 test2 test3 test4)).to include(node.name) end end @@ -92,6 +94,7 @@ module DiasporaFederation it "gets xml-ified by #to_xml" do entity = Entities::TestNestedEntity.new(nested_data) xml = entity.to_xml + expect(xml.children).to have_exactly(4).items xml.children.each do |node| expect(%w(asdf test_entity other_entity)).to include(node.name) end @@ -99,5 +102,23 @@ module DiasporaFederation expect(xml.xpath("other_entity")).to have_exactly(2).items end end + + context "xml_name" do + let(:hash) { {test: "test", qwer: "qwer"} } + + it "uses xml_name for the #to_xml" do + entity = Entities::TestEntityWithXmlName.new(hash) + xml_children = entity.to_xml.children + expect(xml_children).to have_exactly(2).items + xml_children.each do |node| + expect(%w(test asdf)).to include(node.name) + end + end + + it "should not use the xml_name for the #to_h" do + entity = Entities::TestEntityWithXmlName.new(hash) + expect(entity.to_h).to eq(hash) + end + end end end diff --git a/spec/lib/diaspora_federation/properties_dsl_spec.rb b/spec/lib/diaspora_federation/properties_dsl_spec.rb index 8a225f2..cd8a287 100644 --- a/spec/lib/diaspora_federation/properties_dsl_spec.rb +++ b/spec/lib/diaspora_federation/properties_dsl_spec.rb @@ -8,19 +8,12 @@ module DiasporaFederation properties = dsl.class_props expect(properties).to have(1).item expect(properties.first[:name]).to eq(:test) - expect(properties.first[:type]).to eq(String) - end - - it "can name simple properties by string" do - dsl.property "test" - properties = dsl.class_props - expect(properties).to have(1).item - expect(properties.first[:name]).to eq("test") + expect(properties.first[:xml_name]).to eq(:test) expect(properties.first[:type]).to eq(String) end it "will not accept other types for names" do - [1234, true, {}].each do |val| + ["test", 1234, true, {}].each do |val| expect { dsl.property val }.to raise_error PropertiesDSL::InvalidName @@ -34,8 +27,26 @@ module DiasporaFederation properties = dsl.class_props expect(properties).to have(3).items expect(properties.map {|e| e[:name] }).to include(:test, :asdf, :zzzz) + expect(properties.map {|e| e[:xml_name] }).to include(:test, :asdf, :zzzz) properties.each {|e| expect(e[:type]).to eq(String) } end + + it "can add an xml name to simple properties with a symbol" do + dsl.property :test, xml_name: :xml_test + properties = dsl.class_props + expect(properties).to have(1).item + expect(properties.first[:name]).to eq(:test) + expect(properties.first[:xml_name]).to eq(:xml_test) + expect(properties.first[:type]).to eq(String) + end + + it "will not accept other types for xml names" do + ["test", 1234, true, {}].each do |val| + expect { + dsl.property :test, xml_name: val + }.to raise_error PropertiesDSL::InvalidName, "invalid xml_name" + end + end end context "nested entities" do @@ -75,6 +86,12 @@ module DiasporaFederation }.to raise_error PropertiesDSL::InvalidType end end + + it "can not add an xml name to a nested entity" do + expect { + dsl.entity :other, Entities::TestEntity, xml_name: :other_name + }.to raise_error ArgumentError, "xml_name is not supported for nested entities" + end end describe ".default_values" do From ed52108cc7ccd039d5db493d1ed682fb38be3d6d Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Sat, 25 Jul 2015 01:27:17 +0200 Subject: [PATCH 25/33] validate entities after creation if a validator is defined --- lib/diaspora_federation/entity.rb | 22 +++++++++ .../validators/rules/boolean.rb | 2 +- spec/entities.rb | 10 ++++ spec/lib/diaspora_federation/entity_spec.rb | 48 ++++++++++++++----- 4 files changed, 69 insertions(+), 13 deletions(-) diff --git a/lib/diaspora_federation/entity.rb b/lib/diaspora_federation/entity.rb index cbcda87..1d01224 100644 --- a/lib/diaspora_federation/entity.rb +++ b/lib/diaspora_federation/entity.rb @@ -53,7 +53,9 @@ module DiasporaFederation self.class.default_values.merge(data).each do |k, v| instance_variable_set("@#{k}", nilify(v)) if setable?(k, v) end + freeze + validate end # Returns a Hash representing this Entity (attributes => values) @@ -115,6 +117,22 @@ module DiasporaFederation value end + def validate + validator_name = "DiasporaFederation::Validators::#{self.class.name.split('::').last}Validator" + return unless Validators.const_defined? validator_name + + validator_class = Validators.const_get validator_name + validator = validator_class.new self + raise ValidationError, error_message(validator) unless validator.valid? + end + + def error_message(validator) + errors = validator.errors.map do |prop, rule| + "property: #{prop}, value: #{public_send(prop).inspect}, rule: #{rule[:rule]}, with params: #{rule[:params]}" + end + "Failed validation for properties: #{errors.join(' | ')}" + end + # Serialize the Entity into XML elements # @return [Nokogiri::XML::Element] root node def entity_xml @@ -146,5 +164,9 @@ module DiasporaFederation node.content = data unless data.empty? end end + + # Raised, if entity is not valid + class ValidationError < RuntimeError + end end end diff --git a/lib/diaspora_federation/validators/rules/boolean.rb b/lib/diaspora_federation/validators/rules/boolean.rb index 64eb9f9..ecc2b77 100644 --- a/lib/diaspora_federation/validators/rules/boolean.rb +++ b/lib/diaspora_federation/validators/rules/boolean.rb @@ -2,7 +2,7 @@ module Validation module Rule class Boolean def error_key - :numeric + :boolean end def valid_value?(value) diff --git a/spec/entities.rb b/spec/entities.rb index 8ec2002..2ecb901 100644 --- a/spec/entities.rb +++ b/spec/entities.rb @@ -26,4 +26,14 @@ module DiasporaFederation property :qwer, xml_name: :asdf end end + + module Validators + class TestDefaultEntityValidator < Validation::Validator + include Validation + + rule :test1, regular_expression: {regex: /\A[^;]{,32}\z/} + rule :test2, :not_nil + rule :test3, :boolean + end + end end diff --git a/spec/lib/diaspora_federation/entity_spec.rb b/spec/lib/diaspora_federation/entity_spec.rb index bc168d3..085bfb1 100644 --- a/spec/lib/diaspora_federation/entity_spec.rb +++ b/spec/lib/diaspora_federation/entity_spec.rb @@ -18,20 +18,22 @@ module DiasporaFederation }.to raise_error ArgumentError, "missing required properties: test1, test2" end - it "sets the defaults" do - entity = Entities::TestDefaultEntity.new(test1: 1, test2: 2) - expect(entity.test3).to be_truthy - end + context "defaults" do + it "sets the defaults" do + entity = Entities::TestDefaultEntity.new(test1: "1", test2: "2") + expect(entity.test3).to be_truthy + end - it "handles callable defaults" do - entity = Entities::TestDefaultEntity.new(test1: 1, test2: 2) - expect(entity.test4).to be_truthy - end + it "handles callable defaults" do + entity = Entities::TestDefaultEntity.new(test1: "1", test2: "2") + expect(entity.test4).to be_truthy + end - it "uses provided values over defaults" do - entity = Entities::TestDefaultEntity.new(data) - expect(entity.test3).to be_falsey - expect(entity.test4).to be_falsey + it "uses provided values over defaults" do + entity = Entities::TestDefaultEntity.new(data) + expect(entity.test3).to be_falsey + expect(entity.test4).to be_falsey + end end it "sets nil if string is empty" do @@ -39,6 +41,28 @@ module DiasporaFederation entity = Entities::TestDefaultEntity.new(data) expect(entity.test1).to be_nil end + + context "validation" do + let(:invalid_data) { {test1: "as;df", test2: nil, test3: "no boolean"} } + + it "validates the entity and raise an error with failed properties if not valid" do + expect { + Entities::TestDefaultEntity.new(invalid_data) + }.to raise_error Entity::ValidationError, /Failed validation for properties:.*test1.*\|.*test2.*\|.*test3/ + end + + it "contains the failed rule" do + expect { + Entities::TestDefaultEntity.new(invalid_data) + }.to raise_error Entity::ValidationError, /property: test2, value: nil, rule: not_nil, with params: \{\}/ + end + + it "contains the params of the failed rule" do + expect { + Entities::TestDefaultEntity.new(invalid_data) + }.to raise_error Entity::ValidationError, /rule: regular_expression, with params: \{:regex=>.*\}/ + end + end end describe "#to_h" do From ce39616265abaa6aa77a972e7c4861e63f615f9f Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Sat, 25 Jul 2015 22:17:52 +0200 Subject: [PATCH 26/33] fix tag count validator --- lib/diaspora_federation/validators/rules/tag_count.rb | 2 +- .../validators/rules/tag_count_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/diaspora_federation/validators/rules/tag_count.rb b/lib/diaspora_federation/validators/rules/tag_count.rb index e46fd7c..9a74c15 100644 --- a/lib/diaspora_federation/validators/rules/tag_count.rb +++ b/lib/diaspora_federation/validators/rules/tag_count.rb @@ -20,7 +20,7 @@ module Validation end def valid_value?(value) - value.count("#") <= params[:maximum] + value.nil? || value.count("#") <= params[:maximum] end end end diff --git a/spec/lib/diaspora_federation/validators/rules/tag_count_spec.rb b/spec/lib/diaspora_federation/validators/rules/tag_count_spec.rb index c301b41..e7a1ae7 100644 --- a/spec/lib/diaspora_federation/validators/rules/tag_count_spec.rb +++ b/spec/lib/diaspora_federation/validators/rules/tag_count_spec.rb @@ -41,5 +41,13 @@ describe Validation::Rule::TagCount do expect(validator).not_to be_valid expect(validator.errors).to include(:tags) end + + it "validates if tags are nil" do + validator = Validation::Validator.new(OpenStruct.new(tags: nil)) + validator.rule(:tags, tag_count: {maximum: 5}) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end end end From dd6b938f2ea94604c1635283caf3569ce76b29b0 Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Sun, 26 Jul 2015 01:19:09 +0200 Subject: [PATCH 27/33] more documentation for validation --- lib/diaspora_federation/entities/person.rb | 2 ++ lib/diaspora_federation/entities/profile.rb | 2 ++ lib/diaspora_federation/entity.rb | 6 ++++++ lib/diaspora_federation/validators.rb | 3 +++ .../validators/person_validator.rb | 1 + .../validators/profile_validator.rb | 1 + lib/diaspora_federation/validators/rules/birthday.rb | 10 ++++++++++ lib/diaspora_federation/validators/rules/boolean.rb | 10 ++++++++++ .../validators/rules/diaspora_id.rb | 5 ++++- lib/diaspora_federation/validators/rules/guid.rb | 10 ++++++++++ lib/diaspora_federation/validators/rules/not_nil.rb | 5 +++++ .../validators/rules/public_key.rb | 11 ++++++++++- lib/diaspora_federation/validators/rules/tag_count.rb | 6 ++++++ 13 files changed, 70 insertions(+), 2 deletions(-) diff --git a/lib/diaspora_federation/entities/person.rb b/lib/diaspora_federation/entities/person.rb index ebbcbcc..32a9080 100644 --- a/lib/diaspora_federation/entities/person.rb +++ b/lib/diaspora_federation/entities/person.rb @@ -1,6 +1,8 @@ module DiasporaFederation module Entities # this entity contains the base data of a person + # + # @see Validators::PersonValidator class Person < Entity # @!attribute [r] guid # @see HCard#guid diff --git a/lib/diaspora_federation/entities/profile.rb b/lib/diaspora_federation/entities/profile.rb index c819cff..3a728b8 100644 --- a/lib/diaspora_federation/entities/profile.rb +++ b/lib/diaspora_federation/entities/profile.rb @@ -1,6 +1,8 @@ module DiasporaFederation module Entities # this entity contains all the profile data of a person + # + # @see Validators::ProfileValidator class Profile < Entity # @!attribute [r] diaspora_id # The diaspora ID of the person diff --git a/lib/diaspora_federation/entity.rb b/lib/diaspora_federation/entity.rb index 1d01224..86f980b 100644 --- a/lib/diaspora_federation/entity.rb +++ b/lib/diaspora_federation/entity.rb @@ -38,6 +38,12 @@ module DiasporaFederation # Initializes the Entity with the given attribute hash and freezes the created # instance it returns. # + # After creation, the entity is validated against a Validator, if one is defined. + # The Validator needs to be in the {DiasporaFederation::Validators} namespace and + # named like "Validator". Only valid entities can be created. + # + # @see DiasporaFederation::Validators + # # @note Attributes not defined as part of the class definition ({PropertiesDSL#property}, # {PropertiesDSL#entity}) get discarded silently. # diff --git a/lib/diaspora_federation/validators.rb b/lib/diaspora_federation/validators.rb index 79d6f40..416e992 100644 --- a/lib/diaspora_federation/validators.rb +++ b/lib/diaspora_federation/validators.rb @@ -23,6 +23,9 @@ require "diaspora_federation/validators/rules/tag_count" module DiasporaFederation # Validators to perform basic sanity-checks on {DiasporaFederation::Entities federation entities}. + # + # The Validators are mapped with the entities by name. The naming schema + # is "Validator". module Validators end end diff --git a/lib/diaspora_federation/validators/person_validator.rb b/lib/diaspora_federation/validators/person_validator.rb index 33c9957..1995b21 100644 --- a/lib/diaspora_federation/validators/person_validator.rb +++ b/lib/diaspora_federation/validators/person_validator.rb @@ -1,5 +1,6 @@ module DiasporaFederation module Validators + # This validates a {Entities::Person} class PersonValidator < Validation::Validator include Validation diff --git a/lib/diaspora_federation/validators/profile_validator.rb b/lib/diaspora_federation/validators/profile_validator.rb index 4795f5a..fd32979 100644 --- a/lib/diaspora_federation/validators/profile_validator.rb +++ b/lib/diaspora_federation/validators/profile_validator.rb @@ -1,5 +1,6 @@ module DiasporaFederation module Validators + # This validates a {Entities::Profile} class ProfileValidator < Validation::Validator include Validation diff --git a/lib/diaspora_federation/validators/rules/birthday.rb b/lib/diaspora_federation/validators/rules/birthday.rb index 89f97fe..682719c 100644 --- a/lib/diaspora_federation/validators/rules/birthday.rb +++ b/lib/diaspora_federation/validators/rules/birthday.rb @@ -2,11 +2,20 @@ require "date" module Validation module Rule + # Birthday validation rule + # + # Valid is: + # * nil or an empty +String+ + # * a +Date+ object + # * a +String+ with the format "yyyy-mm-dd" and is a valid +Date+, example: 2015-07-25 class Birthday + # The error key for this rule + # @return [Symbol] error key def error_key :birthday end + # Determines if value is a valid birthday date def valid_value?(value) return true if value.nil? || (value.is_a?(String) && value.empty?) return true if value.is_a? Date @@ -20,6 +29,7 @@ module Validation end # This rule has no params + # @return [Hash] params def params {} end diff --git a/lib/diaspora_federation/validators/rules/boolean.rb b/lib/diaspora_federation/validators/rules/boolean.rb index ecc2b77..f2f4918 100644 --- a/lib/diaspora_federation/validators/rules/boolean.rb +++ b/lib/diaspora_federation/validators/rules/boolean.rb @@ -1,10 +1,19 @@ module Validation module Rule + # Boolean validation rule + # + # Valid is: + # * a +String+: "true", "false", "t", "f", "yes", "no", "y", "n", "1", "0" + # * a +Fixnum+: 1 or 0 + # * a +Boolean+: true or false class Boolean + # The error key for this rule + # @return [Symbol] error key def error_key :boolean end + # Determines if value is a valid +boolean+ def valid_value?(value) return false if value.nil? @@ -20,6 +29,7 @@ module Validation end # This rule has no params + # @return [Hash] params def params {} end diff --git a/lib/diaspora_federation/validators/rules/diaspora_id.rb b/lib/diaspora_federation/validators/rules/diaspora_id.rb index f17f7b7..591c621 100644 --- a/lib/diaspora_federation/validators/rules/diaspora_id.rb +++ b/lib/diaspora_federation/validators/rules/diaspora_id.rb @@ -5,6 +5,7 @@ module Validation # This rule is based on https://github.com/zombor/Validator/blob/master/lib/validation/rule/email.rb # which was adapted from https://github.com/emmanuel/aequitas/blob/master/lib/aequitas/rule/format/email_address.rb class DiasporaId + # The Regex for a valid diaspora ID DIASPORA_ID = begin letter = "a-zA-Z" digit = "0-9" @@ -25,16 +26,18 @@ module Validation end # The error key for this rule + # @return [Symbol] error key def error_key :diaspora_id end - # Determines if value is a valid email + # Determines if value is a valid diaspora ID def valid_value?(value) !DIASPORA_ID.match(value).nil? end # This rule has no params + # @return [Hash] params def params {} end diff --git a/lib/diaspora_federation/validators/rules/guid.rb b/lib/diaspora_federation/validators/rules/guid.rb index f559fc6..73077fa 100644 --- a/lib/diaspora_federation/validators/rules/guid.rb +++ b/lib/diaspora_federation/validators/rules/guid.rb @@ -1,15 +1,25 @@ module Validation module Rule + # GUID validation rule + # + # Valid is a +String+ that is at least 16 chars long and contains only: + # * Letters: a-z + # * Numbers: 0-9 + # * Special chars: '-', '_', '@', '.' and ':' class Guid + # The error key for this rule + # @return [Symbol] error key def error_key :guid end + # Determines if value is a valid +GUID+ def valid_value?(value) value.is_a?(String) && value.downcase =~ /\A[0-9a-z\-_@.:]{16,}\z/ end # This rule has no params + # @return [Hash] params def params {} end diff --git a/lib/diaspora_federation/validators/rules/not_nil.rb b/lib/diaspora_federation/validators/rules/not_nil.rb index d3866ae..8d37d7e 100644 --- a/lib/diaspora_federation/validators/rules/not_nil.rb +++ b/lib/diaspora_federation/validators/rules/not_nil.rb @@ -1,15 +1,20 @@ module Validation module Rule + # Validates that a property is not +nil+ class NotNil + # The error key for this rule + # @return [Symbol] error key def error_key :not_nil end + # Determines if value is not nil def valid_value?(value) !value.nil? end # This rule has no params + # @return [Hash] params def params {} end diff --git a/lib/diaspora_federation/validators/rules/public_key.rb b/lib/diaspora_federation/validators/rules/public_key.rb index e7681ff..e2536e0 100644 --- a/lib/diaspora_federation/validators/rules/public_key.rb +++ b/lib/diaspora_federation/validators/rules/public_key.rb @@ -1,11 +1,19 @@ module Validation module Rule + # Public key validation rule + # + # A valid key must: + # * start with "-----BEGIN PUBLIC KEY-----" and end with "-----END PUBLIC KEY-----" + # or + # * start with "-----BEGIN RSA PUBLIC KEY-----" and end with "-----END RSA PUBLIC KEY-----" class PublicKey + # The error key for this rule + # @return [Symbol] error key def error_key :public_key end - # allow both "PUBLIC KEY" and "RSA PUBLIC KEY" + # Determines if value is a valid public key def valid_value?(value) (value.strip.start_with?("-----BEGIN PUBLIC KEY-----") && value.strip.end_with?("-----END PUBLIC KEY-----")) || @@ -14,6 +22,7 @@ module Validation end # This rule has no params + # @return [Hash] params def params {} end diff --git a/lib/diaspora_federation/validators/rules/tag_count.rb b/lib/diaspora_federation/validators/rules/tag_count.rb index 9a74c15..6f100a8 100644 --- a/lib/diaspora_federation/validators/rules/tag_count.rb +++ b/lib/diaspora_federation/validators/rules/tag_count.rb @@ -2,7 +2,10 @@ module Validation module Rule # Rule for validating the number of tags in a string. # Only the "#" characters will be counted. + # The string can be nil. class TagCount + # This rule must have a +maximum+ param + # @return [Hash] params attr_reader :params # @param [Hash] params @@ -15,10 +18,13 @@ module Validation @params = params end + # The error key for this rule + # @return [Symbol] error key def error_key :tag_count end + # Determines if value doesn't have more than +maximum+ tags def valid_value?(value) value.nil? || value.count("#") <= params[:maximum] end From d7a5e71ce6337a730bfa57a1d3629b248058d055 Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Mon, 27 Jul 2015 00:47:31 +0200 Subject: [PATCH 28/33] refactor shared examples and use more of them --- .../validators/rules/public_key.rb | 10 +- .../validators/person_validator_spec.rb | 42 ++----- .../validators/profile_validator_spec.rb | 36 +----- .../validators/rules/public_key_spec.rb | 16 +++ spec/support/shared_validator_specs.rb | 118 +++++++++++++----- 5 files changed, 121 insertions(+), 101 deletions(-) diff --git a/lib/diaspora_federation/validators/rules/public_key.rb b/lib/diaspora_federation/validators/rules/public_key.rb index e2536e0..8601fc9 100644 --- a/lib/diaspora_federation/validators/rules/public_key.rb +++ b/lib/diaspora_federation/validators/rules/public_key.rb @@ -15,10 +15,12 @@ module Validation # Determines if value is a valid public key def valid_value?(value) - (value.strip.start_with?("-----BEGIN PUBLIC KEY-----") && - value.strip.end_with?("-----END PUBLIC KEY-----")) || - (value.strip.start_with?("-----BEGIN RSA PUBLIC KEY-----") && - value.strip.end_with?("-----END RSA PUBLIC KEY-----")) + !value.nil? && ( + (value.strip.start_with?("-----BEGIN PUBLIC KEY-----") && + value.strip.end_with?("-----END PUBLIC KEY-----")) || + (value.strip.start_with?("-----BEGIN RSA PUBLIC KEY-----") && + value.strip.end_with?("-----END RSA PUBLIC KEY-----")) + ) end # This rule has no params diff --git a/spec/lib/diaspora_federation/validators/person_validator_spec.rb b/spec/lib/diaspora_federation/validators/person_validator_spec.rb index 4240f94..4f62ce0 100644 --- a/spec/lib/diaspora_federation/validators/person_validator_spec.rb +++ b/spec/lib/diaspora_federation/validators/person_validator_spec.rb @@ -1,5 +1,7 @@ module DiasporaFederation describe Validators::PersonValidator do + let(:entity) { :person_entity } + it "validates a well-formed instance" do instance = OpenStruct.new(FactoryGirl.attributes_for(:person_entity)) validator = Validators::PersonValidator.new(instance) @@ -8,33 +10,17 @@ module DiasporaFederation expect(validator.errors).to be_empty end - it_behaves_like "a diaspora_id validator" do - let(:entity) { :person_entity } - let(:validator_class) { Validators::PersonValidator } + it_behaves_like "a diaspora id validator" do let(:property) { :diaspora_id } end it_behaves_like "a guid validator" do - let(:entity) { :person_entity } - let(:validator_class) { Validators::PersonValidator } let(:property) { :guid } end context "#url" do - it "fails for url with special chars" do - instance = OpenStruct.new(FactoryGirl.attributes_for(:person_entity, url: "https://asdf$%.com")) - validator = Validators::PersonValidator.new(instance) - - expect(validator).not_to be_valid - expect(validator.errors).to include(:url) - end - - it "fails for url without scheme" do - instance = OpenStruct.new(FactoryGirl.attributes_for(:person_entity, url: "example.com")) - validator = Validators::PersonValidator.new(instance) - - expect(validator).not_to be_valid - expect(validator.errors).to include(:url) + it_behaves_like "a url validator without path" do + let(:property) { :url } end end @@ -48,22 +34,8 @@ module DiasporaFederation end end - context "#exported_key" do - it "fails for malformed rsa key" do - instance = OpenStruct.new(FactoryGirl.attributes_for(:person_entity, exported_key: "ASDF")) - validator = Validators::PersonValidator.new(instance) - - expect(validator).not_to be_valid - expect(validator.errors).to include(:exported_key) - end - - it "must not be empty" do - instance = OpenStruct.new(FactoryGirl.attributes_for(:person_entity, exported_key: "")) - validator = Validators::PersonValidator.new(instance) - - expect(validator).not_to be_valid - expect(validator.errors).to include(:exported_key) - end + it_behaves_like "a public key validator" do + let(:property) { :exported_key } end end end diff --git a/spec/lib/diaspora_federation/validators/profile_validator_spec.rb b/spec/lib/diaspora_federation/validators/profile_validator_spec.rb index 01fc765..882538e 100644 --- a/spec/lib/diaspora_federation/validators/profile_validator_spec.rb +++ b/spec/lib/diaspora_federation/validators/profile_validator_spec.rb @@ -1,5 +1,7 @@ module DiasporaFederation describe Validators::ProfileValidator do + let(:entity) { :profile_entity } + def profile_stub(data={}) OpenStruct.new(FactoryGirl.attributes_for(:profile_entity).merge(data)) end @@ -11,40 +13,14 @@ module DiasporaFederation expect(validator.errors).to be_empty end - it_behaves_like "a diaspora_id validator" do - let(:entity) { :profile_entity } - let(:validator_class) { Validators::ProfileValidator } + it_behaves_like "a diaspora id validator" do let(:property) { :diaspora_id } end %i(first_name last_name).each do |prop| describe "##{prop}" do - it "allowed to be nil" do - validator = Validators::ProfileValidator.new(profile_stub(prop => nil)) - - expect(validator).to be_valid - expect(validator.errors).to be_empty - end - - it "allowed to contain special chars" do - validator = Validators::ProfileValidator.new(profile_stub(prop => "cool name ©")) - - expect(validator).to be_valid - expect(validator.errors).to be_empty - end - - it "must not exceed 32 chars" do - validator = Validators::ProfileValidator.new(profile_stub(prop => "abcdefghijklmnopqrstuvwxyz_aaaaaaaaaa")) - - expect(validator).not_to be_valid - expect(validator.errors).to include(prop) - end - - it "must not contain semicolons" do - validator = Validators::ProfileValidator.new(profile_stub(prop => "asdf;qwer;yxcv")) - - expect(validator).not_to be_valid - expect(validator.errors).to include(prop) + it_behaves_like "a name validator" do + let(:property) { prop } end end end @@ -91,8 +67,6 @@ module DiasporaFederation %i(searchable nsfw).each do |prop| describe "##{prop}" do it_behaves_like "a boolean validator" do - let(:entity) { :profile_entity } - let(:validator_class) { Validators::ProfileValidator } let(:property) { prop } end end diff --git a/spec/lib/diaspora_federation/validators/rules/public_key_spec.rb b/spec/lib/diaspora_federation/validators/rules/public_key_spec.rb index 5d1ada0..efe461c 100644 --- a/spec/lib/diaspora_federation/validators/rules/public_key_spec.rb +++ b/spec/lib/diaspora_federation/validators/rules/public_key_spec.rb @@ -45,6 +45,22 @@ describe Validation::Rule::Guid do expect(validator).not_to be_valid expect(validator.errors).to include(:key) end + + it "fails if the key is empty" do + validator = Validation::Validator.new(OpenStruct.new(key: "")) + validator.rule(:key, :public_key) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:key) + end + + it "fails if the key is nil" do + validator = Validation::Validator.new(OpenStruct.new(key: nil)) + validator.rule(:key, :public_key) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:key) + end end end end diff --git a/spec/support/shared_validator_specs.rb b/spec/support/shared_validator_specs.rb index 07b7845..9675b84 100644 --- a/spec/support/shared_validator_specs.rb +++ b/spec/support/shared_validator_specs.rb @@ -1,26 +1,21 @@ -def entity_stub(entity, property, val=nil) +def entity_stub(entity, property, val) instance = OpenStruct.new(FactoryGirl.attributes_for(entity)) - instance.public_send("#{property}=", val) unless val.nil? + instance.public_send("#{property}=", val) instance end -shared_examples "a diaspora_id validator" do - it "validates a well-formed diaspora_id" do - validator = validator_class.new(entity_stub(entity, property)) +shared_examples "a diaspora id validator" do + it "must not be nil or empty" do + [nil, ""].each do |val| + validator = described_class.new(entity_stub(entity, property, val)) - expect(validator).to be_valid - expect(validator.errors).to be_empty - end - - it "must not be empty" do - validator = validator_class.new(entity_stub(entity, property, "")) - - expect(validator).not_to be_valid - expect(validator.errors).to include(property) + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end end it "must be a valid diaspora id" do - validator = validator_class.new(entity_stub(entity, property, "i am a weird diaspora id @@@ ### 12345")) + validator = described_class.new(entity_stub(entity, property, "i am a weird diaspora id @@@ ### 12345")) expect(validator).not_to be_valid expect(validator.errors).to include(property) @@ -28,46 +23,41 @@ shared_examples "a diaspora_id validator" do end shared_examples "a guid validator" do - it "validates a well-formed guid" do - validator = validator_class.new(entity_stub(entity, property)) - - expect(validator).to be_valid - expect(validator.errors).to be_empty - end - it "validates a well-formed guid from redmatrix" do - validator = validator_class.new(entity_stub(entity, property, "1234567890ABCDefgh_ijkl-mnopQR@example.com:3000")) + validator = described_class.new(entity_stub(entity, property, "1234567890ABCDefgh_ijkl-mnopQR@example.com:3000")) expect(validator).to be_valid expect(validator.errors).to be_empty end it "must be at least 16 chars" do - validator = validator_class.new(entity_stub(entity, property, "aaaaaa")) + validator = described_class.new(entity_stub(entity, property, "aaaaaa")) expect(validator).not_to be_valid expect(validator.errors).to include(property) end it "must only contain [0-9a-z-_@.:]" do - validator = validator_class.new(entity_stub(entity, property, "zzz+-#*$$")) + validator = described_class.new(entity_stub(entity, property, "zzz+-#*$$")) expect(validator).not_to be_valid expect(validator.errors).to include(property) end - it "must not be empty" do - validator = validator_class.new(entity_stub(entity, property, "")) + it "must not be nil or empty" do + [nil, ""].each do |val| + validator = described_class.new(entity_stub(entity, property, val)) - expect(validator).not_to be_valid - expect(validator.errors).to include(property) + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end end end shared_examples "a boolean validator" do it "validates a well-formed boolean" do [true, "true", false, "false"].each do |val| - validator = validator_class.new(entity_stub(entity, property, val)) + validator = described_class.new(entity_stub(entity, property, val)) expect(validator).to be_valid expect(validator.errors).to be_empty @@ -76,10 +66,76 @@ shared_examples "a boolean validator" do it "must not be an arbitrary string or other object" do ["asdf", Time.zone.today, 1234].each do |val| - validator = validator_class.new(entity_stub(entity, property, val)) + validator = described_class.new(entity_stub(entity, property, val)) expect(validator).not_to be_valid expect(validator.errors).to include(property) end end end + +shared_examples "a public key validator" do + it "fails for malformed rsa key" do + validator = described_class.new(entity_stub(entity, property, "ASDF")) + + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end + + it "must not be nil or empty" do + [nil, ""].each do |val| + validator = described_class.new(entity_stub(entity, property, val)) + + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end + end +end + +shared_examples "a name validator" do + it "is allowed to be nil or empty" do + [nil, ""].each do |val| + validator = described_class.new(entity_stub(entity, property, val)) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + end + + it "is allowed to contain special chars" do + validator = described_class.new(entity_stub(entity, property, "cool name ©")) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "must not exceed 32 chars" do + validator = described_class.new(entity_stub(entity, property, "abcdefghijklmnopqrstuvwxyz_aaaaaaaaaa")) + + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end + + it "must not contain semicolons" do + validator = described_class.new(entity_stub(entity, property, "asdf;qwer;yxcv")) + + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end +end + +shared_examples "a url validator without path" do + it "fails for url with special chars" do + validator = described_class.new(entity_stub(entity, property, "https://asdf$%.com")) + + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end + + it "fails for url without scheme" do + validator = described_class.new(entity_stub(entity, property, "example.com")) + + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end +end From c1e700d560eac772123446ddd0c3e788083ef9cc Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Mon, 27 Jul 2015 03:47:56 +0200 Subject: [PATCH 29/33] add own URI validation rule that allows nil --- lib/diaspora_federation/validators.rb | 2 +- .../validators/person_validator.rb | 2 +- .../validators/rules/uri.rb | 41 +++++++++++++ .../validators/rules/birthday_spec.rb | 4 ++ .../validators/rules/boolean_spec.rb | 4 ++ .../validators/rules/diaspora_id_spec.rb | 4 ++ .../validators/rules/guid_spec.rb | 4 ++ .../validators/rules/not_nil_spec.rb | 4 ++ .../validators/rules/public_key_spec.rb | 6 +- .../validators/rules/tag_count_spec.rb | 4 ++ .../validators/rules/uri_spec.rb | 61 +++++++++++++++++++ spec/support/shared_validator_specs.rb | 9 +++ 12 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 lib/diaspora_federation/validators/rules/uri.rb create mode 100644 spec/lib/diaspora_federation/validators/rules/uri_spec.rb diff --git a/lib/diaspora_federation/validators.rb b/lib/diaspora_federation/validators.rb index 416e992..8fea8c3 100644 --- a/lib/diaspora_federation/validators.rb +++ b/lib/diaspora_federation/validators.rb @@ -1,6 +1,5 @@ require "validation" require "validation/rule/regular_expression" -require "validation/rule/uri" # +valid+ gem namespace module Validation @@ -20,6 +19,7 @@ require "diaspora_federation/validators/rules/guid" require "diaspora_federation/validators/rules/not_nil" require "diaspora_federation/validators/rules/public_key" require "diaspora_federation/validators/rules/tag_count" +require "diaspora_federation/validators/rules/uri" module DiasporaFederation # Validators to perform basic sanity-checks on {DiasporaFederation::Entities federation entities}. diff --git a/lib/diaspora_federation/validators/person_validator.rb b/lib/diaspora_federation/validators/person_validator.rb index 1995b21..4ccd12a 100644 --- a/lib/diaspora_federation/validators/person_validator.rb +++ b/lib/diaspora_federation/validators/person_validator.rb @@ -8,7 +8,7 @@ module DiasporaFederation rule :diaspora_id, :diaspora_id - rule :url, :URI + rule :url, %i(not_nil URI) rule :profile, :not_nil diff --git a/lib/diaspora_federation/validators/rules/uri.rb b/lib/diaspora_federation/validators/rules/uri.rb new file mode 100644 index 0000000..97d9a06 --- /dev/null +++ b/lib/diaspora_federation/validators/rules/uri.rb @@ -0,0 +1,41 @@ +module Validation + module Rule + # URI validation rule + # + # This rule is based on https://github.com/zombor/Validator/blob/master/lib/validation/rule/uri.rb + # + # It allows +nil+, so maybe add an additional {Rule::NotNil} rule. + class URI + # @param [Array] parts the parts that are required + def initialize(parts=%i(scheme host)) + @required_parts = parts + end + + # The error key for this rule + # @return [Symbol] error key + def error_key + :URI + end + + # This rule has a +required_elements+ param + # @return [Hash] params + def params + {required_elements: @required_parts} + end + + # Determines if value is a valid URI + def valid_value?(uri_string) + return true if uri_string.nil? + + uri = URI(uri_string) + @required_parts.each do |part| + return false if uri.public_send(part).nil? || uri.public_send(part).empty? + end + + true + rescue ::URI::InvalidURIError + false + end + end + end +end diff --git a/spec/lib/diaspora_federation/validators/rules/birthday_spec.rb b/spec/lib/diaspora_federation/validators/rules/birthday_spec.rb index e4811cc..6a65a6b 100644 --- a/spec/lib/diaspora_federation/validators/rules/birthday_spec.rb +++ b/spec/lib/diaspora_federation/validators/rules/birthday_spec.rb @@ -6,6 +6,10 @@ describe Validation::Rule::Birthday do }.to raise_error ArgumentError end + it "has an error key" do + expect(described_class.new.error_key).to eq(:birthday) + end + context "validation" do it "validates a date object" do validator = Validation::Validator.new(OpenStruct.new(birthday: Date.new)) diff --git a/spec/lib/diaspora_federation/validators/rules/boolean_spec.rb b/spec/lib/diaspora_federation/validators/rules/boolean_spec.rb index dc32f9a..74dc2e5 100644 --- a/spec/lib/diaspora_federation/validators/rules/boolean_spec.rb +++ b/spec/lib/diaspora_federation/validators/rules/boolean_spec.rb @@ -6,6 +6,10 @@ describe Validation::Rule::Boolean do }.to raise_error ArgumentError end + it "has an error key" do + expect(described_class.new.error_key).to eq(:boolean) + end + context "validation" do context "strings" do it "validates boolean-esque strings" do diff --git a/spec/lib/diaspora_federation/validators/rules/diaspora_id_spec.rb b/spec/lib/diaspora_federation/validators/rules/diaspora_id_spec.rb index f1f7a0e..072004e 100644 --- a/spec/lib/diaspora_federation/validators/rules/diaspora_id_spec.rb +++ b/spec/lib/diaspora_federation/validators/rules/diaspora_id_spec.rb @@ -6,6 +6,10 @@ describe Validation::Rule::DiasporaId do }.to raise_error ArgumentError end + it "has an error key" do + expect(described_class.new.error_key).to eq(:diaspora_id) + end + context "validation" do it "validates a normal diaspora id" do validator = Validation::Validator.new(OpenStruct.new(diaspora_id: "some_user@example.com")) diff --git a/spec/lib/diaspora_federation/validators/rules/guid_spec.rb b/spec/lib/diaspora_federation/validators/rules/guid_spec.rb index 5cd0b82..272adfb 100644 --- a/spec/lib/diaspora_federation/validators/rules/guid_spec.rb +++ b/spec/lib/diaspora_federation/validators/rules/guid_spec.rb @@ -6,6 +6,10 @@ describe Validation::Rule::Guid do }.to raise_error ArgumentError end + it "has an error key" do + expect(described_class.new.error_key).to eq(:guid) + end + context "validation" do it "validates a string at least 16 chars long, consisting of [0-9a-f] (diaspora)" do validator = Validation::Validator.new(OpenStruct.new(guid: "abcdef0123456789")) diff --git a/spec/lib/diaspora_federation/validators/rules/not_nil_spec.rb b/spec/lib/diaspora_federation/validators/rules/not_nil_spec.rb index 67d5646..ae5d1e6 100644 --- a/spec/lib/diaspora_federation/validators/rules/not_nil_spec.rb +++ b/spec/lib/diaspora_federation/validators/rules/not_nil_spec.rb @@ -6,6 +6,10 @@ describe Validation::Rule::NotNil do }.to raise_error ArgumentError end + it "has an error key" do + expect(described_class.new.error_key).to eq(:not_nil) + end + context "validation" do it "validates a string " do validator = Validation::Validator.new(OpenStruct.new(not_nil: "abcd")) diff --git a/spec/lib/diaspora_federation/validators/rules/public_key_spec.rb b/spec/lib/diaspora_federation/validators/rules/public_key_spec.rb index efe461c..f4eb9ca 100644 --- a/spec/lib/diaspora_federation/validators/rules/public_key_spec.rb +++ b/spec/lib/diaspora_federation/validators/rules/public_key_spec.rb @@ -1,4 +1,4 @@ -describe Validation::Rule::Guid do +describe Validation::Rule::PublicKey do it "will not accept parameters" do validator = Validation::Validator.new({}) expect { @@ -6,6 +6,10 @@ describe Validation::Rule::Guid do }.to raise_error ArgumentError end + it "has an error key" do + expect(described_class.new.error_key).to eq(:public_key) + end + context "validation" do ["PUBLIC KEY", "RSA PUBLIC KEY"].each do |key_type| context key_type do diff --git a/spec/lib/diaspora_federation/validators/rules/tag_count_spec.rb b/spec/lib/diaspora_federation/validators/rules/tag_count_spec.rb index e7a1ae7..a54e846 100644 --- a/spec/lib/diaspora_federation/validators/rules/tag_count_spec.rb +++ b/spec/lib/diaspora_federation/validators/rules/tag_count_spec.rb @@ -15,6 +15,10 @@ describe Validation::Rule::TagCount do end end + it "has an error key" do + expect(described_class.new(maximum: 5).error_key).to eq(:tag_count) + end + context "validation" do let(:tag_str) { "#i #love #tags" } diff --git a/spec/lib/diaspora_federation/validators/rules/uri_spec.rb b/spec/lib/diaspora_federation/validators/rules/uri_spec.rb new file mode 100644 index 0000000..47cdd03 --- /dev/null +++ b/spec/lib/diaspora_federation/validators/rules/uri_spec.rb @@ -0,0 +1,61 @@ +describe Validation::Rule::URI do + it "has default params" do + expect(described_class.new.params).to eq(required_elements: %i(scheme host)) + end + + it "has an error key" do + expect(described_class.new.error_key).to eq(:URI) + end + + context "validation" do + it "validates a valid uri" do + validator = Validation::Validator.new(OpenStruct.new(uri: "http://example.com")) + validator.rule(:uri, :URI) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "validates nil" do + validator = Validation::Validator.new(OpenStruct.new(uri: nil)) + validator.rule(:uri, :URI) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "fails when given an invalid uri" do + validator = Validation::Validator.new(OpenStruct.new(uri: "foo:/%urim")) + validator.rule(:uri, :URI) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:uri) + end + + context "part validation" do + it "fails to validate when given a uri without a host" do + validator = Validation::Validator.new(OpenStruct.new(uri: "http:foo@")) + validator.rule(:uri, :URI) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:uri) + end + + it "fails to validate when given a uri without a scheme" do + validator = Validation::Validator.new(OpenStruct.new(uri: "example.com")) + validator.rule(:uri, :URI) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:uri) + end + + it "fails to validate when given a uri without a path" do + validator = Validation::Validator.new(OpenStruct.new(uri: "http://example.com")) + validator.rule(:uri, URI: %i(host path)) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:uri) + end + end + end +end diff --git a/spec/support/shared_validator_specs.rb b/spec/support/shared_validator_specs.rb index 9675b84..e2ce8fc 100644 --- a/spec/support/shared_validator_specs.rb +++ b/spec/support/shared_validator_specs.rb @@ -125,6 +125,15 @@ shared_examples "a name validator" do end shared_examples "a url validator without path" do + it "must not be nil or empty" do + [nil, ""].each do |val| + validator = described_class.new(entity_stub(entity, property, val)) + + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end + end + it "fails for url with special chars" do validator = described_class.new(entity_stub(entity, property, "https://asdf$%.com")) From 416f322cc71fc60edf41fa115e57289f53b2827e Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Tue, 28 Jul 2015 00:40:04 +0200 Subject: [PATCH 30/33] rename new URI validator to NilableURI and extend existing URI validator --- lib/diaspora_federation/validators.rb | 3 +- .../validators/person_validator.rb | 2 +- .../validators/rules/nilable_uri.rb | 19 +++++++++ .../validators/rules/uri.rb | 41 ------------------- .../{uri_spec.rb => nilable_uri_spec.rb} | 20 ++++----- 5 files changed, 30 insertions(+), 55 deletions(-) create mode 100644 lib/diaspora_federation/validators/rules/nilable_uri.rb delete mode 100644 lib/diaspora_federation/validators/rules/uri.rb rename spec/lib/diaspora_federation/validators/rules/{uri_spec.rb => nilable_uri_spec.rb} (77%) diff --git a/lib/diaspora_federation/validators.rb b/lib/diaspora_federation/validators.rb index 8fea8c3..310c078 100644 --- a/lib/diaspora_federation/validators.rb +++ b/lib/diaspora_federation/validators.rb @@ -1,5 +1,6 @@ require "validation" require "validation/rule/regular_expression" +require "validation/rule/uri" # +valid+ gem namespace module Validation @@ -16,10 +17,10 @@ require "diaspora_federation/validators/rules/birthday" require "diaspora_federation/validators/rules/boolean" require "diaspora_federation/validators/rules/diaspora_id" require "diaspora_federation/validators/rules/guid" +require "diaspora_federation/validators/rules/nilable_uri" require "diaspora_federation/validators/rules/not_nil" require "diaspora_federation/validators/rules/public_key" require "diaspora_federation/validators/rules/tag_count" -require "diaspora_federation/validators/rules/uri" module DiasporaFederation # Validators to perform basic sanity-checks on {DiasporaFederation::Entities federation entities}. diff --git a/lib/diaspora_federation/validators/person_validator.rb b/lib/diaspora_federation/validators/person_validator.rb index 4ccd12a..f344f9e 100644 --- a/lib/diaspora_federation/validators/person_validator.rb +++ b/lib/diaspora_federation/validators/person_validator.rb @@ -8,7 +8,7 @@ module DiasporaFederation rule :diaspora_id, :diaspora_id - rule :url, %i(not_nil URI) + rule :url, %i(not_nil nilableURI) rule :profile, :not_nil diff --git a/lib/diaspora_federation/validators/rules/nilable_uri.rb b/lib/diaspora_federation/validators/rules/nilable_uri.rb new file mode 100644 index 0000000..727d460 --- /dev/null +++ b/lib/diaspora_federation/validators/rules/nilable_uri.rb @@ -0,0 +1,19 @@ +module Validation + module Rule + # URI validation rule + + # It allows +nil+, so maybe add an additional {Rule::NotNil} rule. + class NilableURI < Validation::Rule::URI + # The error key for this rule + # @return [Symbol] error key + def error_key + :nilableURI + end + + # Determines if value is a valid URI + def valid_value?(uri_string) + uri_string.nil? || super + end + end + end +end diff --git a/lib/diaspora_federation/validators/rules/uri.rb b/lib/diaspora_federation/validators/rules/uri.rb deleted file mode 100644 index 97d9a06..0000000 --- a/lib/diaspora_federation/validators/rules/uri.rb +++ /dev/null @@ -1,41 +0,0 @@ -module Validation - module Rule - # URI validation rule - # - # This rule is based on https://github.com/zombor/Validator/blob/master/lib/validation/rule/uri.rb - # - # It allows +nil+, so maybe add an additional {Rule::NotNil} rule. - class URI - # @param [Array] parts the parts that are required - def initialize(parts=%i(scheme host)) - @required_parts = parts - end - - # The error key for this rule - # @return [Symbol] error key - def error_key - :URI - end - - # This rule has a +required_elements+ param - # @return [Hash] params - def params - {required_elements: @required_parts} - end - - # Determines if value is a valid URI - def valid_value?(uri_string) - return true if uri_string.nil? - - uri = URI(uri_string) - @required_parts.each do |part| - return false if uri.public_send(part).nil? || uri.public_send(part).empty? - end - - true - rescue ::URI::InvalidURIError - false - end - end - end -end diff --git a/spec/lib/diaspora_federation/validators/rules/uri_spec.rb b/spec/lib/diaspora_federation/validators/rules/nilable_uri_spec.rb similarity index 77% rename from spec/lib/diaspora_federation/validators/rules/uri_spec.rb rename to spec/lib/diaspora_federation/validators/rules/nilable_uri_spec.rb index 47cdd03..691e4fd 100644 --- a/spec/lib/diaspora_federation/validators/rules/uri_spec.rb +++ b/spec/lib/diaspora_federation/validators/rules/nilable_uri_spec.rb @@ -1,16 +1,12 @@ -describe Validation::Rule::URI do - it "has default params" do - expect(described_class.new.params).to eq(required_elements: %i(scheme host)) - end - +describe Validation::Rule::NilableURI do it "has an error key" do - expect(described_class.new.error_key).to eq(:URI) + expect(described_class.new.error_key).to eq(:nilableURI) end context "validation" do it "validates a valid uri" do validator = Validation::Validator.new(OpenStruct.new(uri: "http://example.com")) - validator.rule(:uri, :URI) + validator.rule(:uri, :nilableURI) expect(validator).to be_valid expect(validator.errors).to be_empty @@ -18,7 +14,7 @@ describe Validation::Rule::URI do it "validates nil" do validator = Validation::Validator.new(OpenStruct.new(uri: nil)) - validator.rule(:uri, :URI) + validator.rule(:uri, :nilableURI) expect(validator).to be_valid expect(validator.errors).to be_empty @@ -26,7 +22,7 @@ describe Validation::Rule::URI do it "fails when given an invalid uri" do validator = Validation::Validator.new(OpenStruct.new(uri: "foo:/%urim")) - validator.rule(:uri, :URI) + validator.rule(:uri, :nilableURI) expect(validator).not_to be_valid expect(validator.errors).to include(:uri) @@ -35,7 +31,7 @@ describe Validation::Rule::URI do context "part validation" do it "fails to validate when given a uri without a host" do validator = Validation::Validator.new(OpenStruct.new(uri: "http:foo@")) - validator.rule(:uri, :URI) + validator.rule(:uri, :nilableURI) expect(validator).not_to be_valid expect(validator.errors).to include(:uri) @@ -43,7 +39,7 @@ describe Validation::Rule::URI do it "fails to validate when given a uri without a scheme" do validator = Validation::Validator.new(OpenStruct.new(uri: "example.com")) - validator.rule(:uri, :URI) + validator.rule(:uri, :nilableURI) expect(validator).not_to be_valid expect(validator.errors).to include(:uri) @@ -51,7 +47,7 @@ describe Validation::Rule::URI do it "fails to validate when given a uri without a path" do validator = Validation::Validator.new(OpenStruct.new(uri: "http://example.com")) - validator.rule(:uri, URI: %i(host path)) + validator.rule(:uri, nilableURI: %i(host path)) expect(validator).not_to be_valid expect(validator.errors).to include(:uri) From c15fee279c90de16cde2ca6e9af3f426dca7f0e0 Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Tue, 28 Jul 2015 01:46:16 +0200 Subject: [PATCH 31/33] extend profile validator --- lib/diaspora_federation/entities/profile.rb | 1 + .../validators/profile_validator.rb | 12 +++- spec/factories.rb | 8 ++- .../validators/profile_validator_spec.rb | 49 ++++++++++++-- spec/support/shared_validator_specs.rb | 65 ++++++++++++++++++- 5 files changed, 125 insertions(+), 10 deletions(-) diff --git a/lib/diaspora_federation/entities/profile.rb b/lib/diaspora_federation/entities/profile.rb index 3a728b8..1ecdd35 100644 --- a/lib/diaspora_federation/entities/profile.rb +++ b/lib/diaspora_federation/entities/profile.rb @@ -23,6 +23,7 @@ module DiasporaFederation # @see HCard#last_name # @return [String] last name property :last_name, default: nil + # @!attribute [r] image_url # @see HCard#photo_large_url # @return [String] url to the big avatar (300x300) diff --git a/lib/diaspora_federation/validators/profile_validator.rb b/lib/diaspora_federation/validators/profile_validator.rb index fd32979..773ee97 100644 --- a/lib/diaspora_federation/validators/profile_validator.rb +++ b/lib/diaspora_federation/validators/profile_validator.rb @@ -11,13 +11,23 @@ module DiasporaFederation rule :first_name, regular_expression: {regex: /\A[^;]{,32}\z/} rule :last_name, regular_expression: {regex: /\A[^;]{,32}\z/} - rule :tag_string, tag_count: {maximum: 5} + # this urls can be relative + rule :image_url, nilableURI: [:path] + rule :image_url_medium, nilableURI: [:path] + rule :image_url_small, nilableURI: [:path] rule :birthday, :birthday + # TODO: replace regex with "length: {maximum: xxx}" but this rule doesn't allow nil now. + rule :gender, regular_expression: {regex: /\A.{,255}\z/} + rule :bio, regular_expression: {regex: /\A.{,65535}\z/} + rule :location, regular_expression: {regex: /\A.{,255}\z/} + rule :searchable, :boolean rule :nsfw, :boolean + + rule :tag_string, tag_count: {maximum: 5} end end end diff --git a/spec/factories.rb b/spec/factories.rb index e46254a..2b38aa7 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -32,9 +32,15 @@ FactoryGirl.define do diaspora_id first_name "my_name" last_name nil - tag_string "#i #love #tags" + image_url "/assets/user/default.png" + image_url_medium "/assets/user/default.png" + image_url_small "/assets/user/default.png" birthday "1988-07-15" + gender "Male" + bio "some text about me" + location "github" searchable true nsfw false + tag_string "#i #love #tags" end end diff --git a/spec/lib/diaspora_federation/validators/profile_validator_spec.rb b/spec/lib/diaspora_federation/validators/profile_validator_spec.rb index 882538e..9f50174 100644 --- a/spec/lib/diaspora_federation/validators/profile_validator_spec.rb +++ b/spec/lib/diaspora_federation/validators/profile_validator_spec.rb @@ -21,17 +21,44 @@ module DiasporaFederation describe "##{prop}" do it_behaves_like "a name validator" do let(:property) { prop } + let(:length) { 32 } end end end - describe "#tag_string" do - it "must not contain more than 5 tags" do - validator = Validators::ProfileValidator.new( - profile_stub(tag_string: "#i #have #too #many #tags #in #my #profile")) + %i(image_url image_url_medium image_url_small).each do |prop| + describe "##{prop}" do + it "is allowed to be nil" do + validator = Validators::ProfileValidator.new(profile_stub(prop => nil)) - expect(validator).not_to be_valid - expect(validator.errors).to include(:tag_string) + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it_behaves_like "a url path validator" do + let(:property) { prop } + end + end + end + + describe "#gender" do + it_behaves_like "a length validator" do + let(:property) { :gender } + let(:length) { 255 } + end + end + + describe "#bio" do + it_behaves_like "a length validator" do + let(:property) { :bio } + let(:length) { 65_535 } + end + end + + describe "#location" do + it_behaves_like "a length validator" do + let(:property) { :location } + let(:length) { 255 } end end @@ -71,5 +98,15 @@ module DiasporaFederation end end end + + describe "#tag_string" do + it "must not contain more than 5 tags" do + validator = Validators::ProfileValidator.new( + profile_stub(tag_string: "#i #have #too #many #tags #in #my #profile")) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:tag_string) + end + end end end diff --git a/spec/support/shared_validator_specs.rb b/spec/support/shared_validator_specs.rb index e2ce8fc..74a12da 100644 --- a/spec/support/shared_validator_specs.rb +++ b/spec/support/shared_validator_specs.rb @@ -4,6 +4,12 @@ def entity_stub(entity, property, val) instance end +ALPHANUMERIC_RANGE = [*"0".."9", *"A".."Z", *"a".."z"] + +def alphanumeric_string(length) + Array.new(length) { ALPHANUMERIC_RANGE.sample }.join +end + shared_examples "a diaspora id validator" do it "must not be nil or empty" do [nil, ""].each do |val| @@ -109,8 +115,15 @@ shared_examples "a name validator" do expect(validator.errors).to be_empty end - it "must not exceed 32 chars" do - validator = described_class.new(entity_stub(entity, property, "abcdefghijklmnopqrstuvwxyz_aaaaaaaaaa")) + it "validates the maximum number of chars" do + validator = described_class.new(entity_stub(entity, property, alphanumeric_string(length))) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "must not exceed the maximum number of chars" do + validator = described_class.new(entity_stub(entity, property, alphanumeric_string(length + 1))) expect(validator).not_to be_valid expect(validator.errors).to include(property) @@ -124,6 +137,38 @@ shared_examples "a name validator" do end end +shared_examples "a length validator" do + it "is allowed to be nil or empty" do + [nil, ""].each do |val| + validator = described_class.new(entity_stub(entity, property, val)) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + end + + it "is allowed to contain special chars" do + validator = described_class.new(entity_stub(entity, property, "cool name ©;:#%")) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "validates the maximum number of chars" do + validator = described_class.new(entity_stub(entity, property, alphanumeric_string(length))) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + it "must not exceed the maximum number of chars" do + validator = described_class.new(entity_stub(entity, property, alphanumeric_string(length + 1))) + + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end +end + shared_examples "a url validator without path" do it "must not be nil or empty" do [nil, ""].each do |val| @@ -148,3 +193,19 @@ shared_examples "a url validator without path" do expect(validator.errors).to include(property) end end + +shared_examples "a url path validator" do + it "fails for url with special chars" do + validator = described_class.new(entity_stub(entity, property, "https://asdf$%.com/some/path")) + + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end + + it "fails for url without path" do + validator = described_class.new(entity_stub(entity, property, "https://example.com")) + + expect(validator).not_to be_valid + expect(validator.errors).to include(property) + end +end From fe704fb9811b700141538f5e0d0c9abb2bd1011c Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Tue, 28 Jul 2015 02:06:09 +0200 Subject: [PATCH 32/33] add webfinger and hcard validators --- lib/diaspora_federation/validators.rb | 3 + .../validators/h_card_validator.rb | 30 ++++++++++ .../validators/web_finger_validator.rb | 20 +++++++ spec/factories.rb | 31 ++++++++++- .../validators/h_card_validator_spec.rb | 55 +++++++++++++++++++ .../validators/web_finger_validator_spec.rb | 45 +++++++++++++++ 6 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 lib/diaspora_federation/validators/h_card_validator.rb create mode 100644 lib/diaspora_federation/validators/web_finger_validator.rb create mode 100644 spec/lib/diaspora_federation/validators/h_card_validator_spec.rb create mode 100644 spec/lib/diaspora_federation/validators/web_finger_validator_spec.rb diff --git a/lib/diaspora_federation/validators.rb b/lib/diaspora_federation/validators.rb index 310c078..4371747 100644 --- a/lib/diaspora_federation/validators.rb +++ b/lib/diaspora_federation/validators.rb @@ -1,5 +1,6 @@ require "validation" require "validation/rule/regular_expression" +require "validation/rule/not_empty" require "validation/rule/uri" # +valid+ gem namespace @@ -31,5 +32,7 @@ module DiasporaFederation end end +require "diaspora_federation/validators/h_card_validator" require "diaspora_federation/validators/person_validator" require "diaspora_federation/validators/profile_validator" +require "diaspora_federation/validators/web_finger_validator" diff --git a/lib/diaspora_federation/validators/h_card_validator.rb b/lib/diaspora_federation/validators/h_card_validator.rb new file mode 100644 index 0000000..029f244 --- /dev/null +++ b/lib/diaspora_federation/validators/h_card_validator.rb @@ -0,0 +1,30 @@ +module DiasporaFederation + module Validators + # This validates a {Discovery::HCard} + # + # @todo activate guid and public key validation after all pod have it in + # the hcard. + # + # @note + class HCardValidator < Validation::Validator + include Validation + + # rule :guid, :guid + + # the name must not contain a semicolon because of mentions + # @{ ; } + rule :full_name, regular_expression: {regex: /\A[^;]{,70}\z/} + rule :first_name, regular_expression: {regex: /\A[^;]{,32}\z/} + rule :last_name, regular_expression: {regex: /\A[^;]{,32}\z/} + + # this urls can be relative + rule :photo_large_url, [:not_nil, nilableURI: [:path]] + rule :photo_medium_url, [:not_nil, nilableURI: [:path]] + rule :photo_small_url, [:not_nil, nilableURI: [:path]] + + # rule :exported_key, :public_key + + rule :searchable, :boolean + end + end +end diff --git a/lib/diaspora_federation/validators/web_finger_validator.rb b/lib/diaspora_federation/validators/web_finger_validator.rb new file mode 100644 index 0000000..a6aa397 --- /dev/null +++ b/lib/diaspora_federation/validators/web_finger_validator.rb @@ -0,0 +1,20 @@ +module DiasporaFederation + module Validators + # This validates a {Discovery::WebFinger} + # + # @note it does not validate the guid and public key, because it will be + # removed in the webfinger + class WebFingerValidator < Validation::Validator + include Validation + + rule :acct_uri, :not_empty + + rule :alias_url, [:not_nil, nilableURI: %i(host path)] + rule :hcard_url, [:not_nil, nilableURI: %i(host path)] + rule :seed_url, %i(not_nil nilableURI) + rule :profile_url, [:not_nil, nilableURI: %i(host path)] + rule :atom_url, [:not_nil, nilableURI: %i(host path)] + rule :salmon_url, [:not_nil, nilableURI: %i(host path)] + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index 2b38aa7..8d53271 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -5,6 +5,7 @@ def r_str end FactoryGirl.define do + sequence(:guid) { UUID.generate :compact } sequence(:diaspora_id) {|n| "person-#{n}-#{r_str}@localhost:3000" } sequence(:public_key) { OpenSSL::PKey::RSA.generate(1024).public_key.export } @@ -17,8 +18,34 @@ FactoryGirl.define do end end + factory :webfinger, class: DiasporaFederation::Discovery::WebFinger do + guid + acct_uri { "acct:#{generate(:diaspora_id)}" } + alias_url "http://localhost:3000/people/0123456789abcdef" + hcard_url "http://localhost:3000/hcard/users/user" + seed_url "http://localhost:3000/" + profile_url "http://localhost:3000/u/user" + atom_url "http://localhost:3000/public/user.atom" + salmon_url "http://localhost:3000/receive/users/0123456789abcdef" + public_key + end + + factory :h_card, class: DiasporaFederation::Discovery::HCard do + guid + nickname "some_name" + full_name "my name" + first_name "my name" + last_name nil + url "http://localhost:3000/" + public_key + photo_large_url "/assets/user/default.png" + photo_medium_url "/assets/user/default.png" + photo_small_url "/assets/user/default.png" + searchable true + end + factory :person_entity, class: DiasporaFederation::Entities::Person do - guid UUID.generate :compact + guid diaspora_id url "http://localhost:3000/" exported_key { generate(:public_key) } @@ -30,7 +57,7 @@ FactoryGirl.define do factory :profile_entity, class: DiasporaFederation::Entities::Profile do diaspora_id - first_name "my_name" + first_name "my name" last_name nil image_url "/assets/user/default.png" image_url_medium "/assets/user/default.png" diff --git a/spec/lib/diaspora_federation/validators/h_card_validator_spec.rb b/spec/lib/diaspora_federation/validators/h_card_validator_spec.rb new file mode 100644 index 0000000..a23887b --- /dev/null +++ b/spec/lib/diaspora_federation/validators/h_card_validator_spec.rb @@ -0,0 +1,55 @@ +module DiasporaFederation + describe Validators::HCardValidator do + let(:entity) { :h_card } + + def hcard_stub(data={}) + OpenStruct.new(FactoryGirl.attributes_for(:h_card).merge(data)) + end + + it "validates a well-formed instance" do + validator = Validators::HCardValidator.new(hcard_stub) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + describe "#full_name" do + it_behaves_like "a name validator" do + let(:property) { :full_name } + let(:length) { 70 } + end + end + + %i(first_name last_name).each do |prop| + describe "##{prop}" do + it_behaves_like "a name validator" do + let(:property) { prop } + let(:length) { 32 } + end + end + end + + %i(photo_large_url photo_medium_url photo_small_url).each do |prop| + describe "##{prop}" do + it "must not be nil or empty" do + [nil, ""].each do |val| + validator = Validators::HCardValidator.new(hcard_stub(prop => val)) + + expect(validator).not_to be_valid + expect(validator.errors).to include(prop) + end + end + + it_behaves_like "a url path validator" do + let(:property) { prop } + end + end + end + + describe "#searchable" do + it_behaves_like "a boolean validator" do + let(:property) { :searchable } + end + end + end +end diff --git a/spec/lib/diaspora_federation/validators/web_finger_validator_spec.rb b/spec/lib/diaspora_federation/validators/web_finger_validator_spec.rb new file mode 100644 index 0000000..b9813c0 --- /dev/null +++ b/spec/lib/diaspora_federation/validators/web_finger_validator_spec.rb @@ -0,0 +1,45 @@ +module DiasporaFederation + describe Validators::WebFingerValidator do + let(:entity) { :webfinger } + + def webfinger_stub(data={}) + OpenStruct.new(FactoryGirl.attributes_for(:webfinger).merge(data)) + end + + it "validates a well-formed instance" do + validator = Validators::WebFingerValidator.new(webfinger_stub) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + + describe "#acct_uri" do + it "fails if it is nil or empty" do + [nil, ""].each do |val| + validator = Validators::WebFingerValidator.new(webfinger_stub(acct_uri: val)) + + expect(validator).not_to be_valid + expect(validator.errors).to include(:acct_uri) + end + end + end + + %i(alias_url hcard_url profile_url atom_url salmon_url).each do |prop| + describe "##{prop}" do + it_behaves_like "a url validator without path" do + let(:property) { prop } + end + + it_behaves_like "a url path validator" do + let(:property) { prop } + end + end + end + + describe "#seed_url" do + it_behaves_like "a url validator without path" do + let(:property) { :seed_url } + end + end + end +end From 2b8aad766d61759ff4365ff25fcc6accc8a7885a Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Wed, 29 Jul 2015 01:48:50 +0200 Subject: [PATCH 33/33] add entities tests and shared examples for entities --- .../discovery/h_card_spec.rb | 39 +++++++++---------- .../discovery/web_finger_spec.rb | 35 ++++++++--------- .../entities/person_spec.rb | 8 ++++ .../entities/profile_spec.rb | 8 ++++ spec/support/shared_entity_specs.rb | 33 ++++++++++++++++ 5 files changed, 85 insertions(+), 38 deletions(-) create mode 100644 spec/lib/diaspora_federation/entities/person_spec.rb create mode 100644 spec/lib/diaspora_federation/entities/profile_spec.rb create mode 100644 spec/support/shared_entity_specs.rb diff --git a/spec/lib/diaspora_federation/discovery/h_card_spec.rb b/spec/lib/diaspora_federation/discovery/h_card_spec.rb index 507a265..687645a 100644 --- a/spec/lib/diaspora_federation/discovery/h_card_spec.rb +++ b/spec/lib/diaspora_federation/discovery/h_card_spec.rb @@ -5,6 +5,23 @@ module DiasporaFederation let(:photo_medium_url) { "#{person.url}/upload/medium.png" } let(:photo_small_url) { "#{person.url}/upload/small.png" } + let(:data) { + { + guid: person.guid, + nickname: person.nickname, + full_name: person.full_name, + url: person.url, + photo_large_url: photo_large_url, + photo_medium_url: photo_medium_url, + photo_small_url: photo_small_url, + public_key: person.serialized_public_key, + searchable: person.searchable, + first_name: person.first_name, + last_name: person.last_name + } + } + let(:klass) { Discovery::HCard } + let(:html) { <<-HTML @@ -92,31 +109,13 @@ module DiasporaFederation HTML } - it "must not create blank instances" do - expect { Discovery::HCard.new({}) }.to raise_error ArgumentError - end + it_behaves_like "an Entity subclass" context "generation" do it "creates an instance from a data hash" do - hcard = Discovery::HCard.new( - guid: person.guid, - nickname: person.nickname, - full_name: person.full_name, - url: person.url, - photo_large_url: photo_large_url, - photo_medium_url: photo_medium_url, - photo_small_url: photo_small_url, - public_key: person.serialized_public_key, - searchable: person.searchable, - first_name: person.first_name, - last_name: person.last_name - ) + hcard = Discovery::HCard.new(data) expect(hcard.to_html).to eq(html) end - - it "fails if nil was given" do - expect { Discovery::HCard.new(nil) }.to raise_error ArgumentError, "expected a Hash" - end end context "parsing" do diff --git a/spec/lib/diaspora_federation/discovery/web_finger_spec.rb b/spec/lib/diaspora_federation/discovery/web_finger_spec.rb index dbc372f..79d3e4f 100644 --- a/spec/lib/diaspora_federation/discovery/web_finger_spec.rb +++ b/spec/lib/diaspora_federation/discovery/web_finger_spec.rb @@ -4,6 +4,21 @@ module DiasporaFederation let(:acct) { "acct:#{person.diaspora_id}" } let(:public_key_base64) { Base64.strict_encode64(person.serialized_public_key) } + let(:data) { + { + acct_uri: "acct:#{person.diaspora_id}", + alias_url: person.alias_url, + hcard_url: person.hcard_url, + seed_url: person.url, + profile_url: person.profile_url, + atom_url: person.atom_url, + salmon_url: person.salmon_url, + guid: person.guid, + public_key: person.serialized_public_key + } + } + let(:klass) { Discovery::WebFinger } + let(:xml) { <<-XML @@ -21,29 +36,13 @@ module DiasporaFederation XML } - it "must not create blank instances" do - expect { Discovery::WebFinger.new({}) }.to raise_error ArgumentError - end + it_behaves_like "an Entity subclass" context "generation" do it "creates a nice XML document" do - wf = Discovery::WebFinger.new( - acct_uri: "acct:#{person.diaspora_id}", - alias_url: person.alias_url, - hcard_url: person.hcard_url, - seed_url: person.url, - profile_url: person.profile_url, - atom_url: person.atom_url, - salmon_url: person.salmon_url, - guid: person.guid, - public_key: person.serialized_public_key - ) + wf = Discovery::WebFinger.new(data) expect(wf.to_xml).to eq(xml) end - - it "fails if nil was given" do - expect { Discovery::WebFinger.new(nil) }.to raise_error ArgumentError, "expected a Hash" - end end context "parsing" do diff --git a/spec/lib/diaspora_federation/entities/person_spec.rb b/spec/lib/diaspora_federation/entities/person_spec.rb new file mode 100644 index 0000000..f4091ec --- /dev/null +++ b/spec/lib/diaspora_federation/entities/person_spec.rb @@ -0,0 +1,8 @@ +module DiasporaFederation + describe Entities::Person do + let(:data) { FactoryGirl.attributes_for(:person_entity) } + let(:klass) { Entities::Person } + + it_behaves_like "an Entity subclass" + end +end diff --git a/spec/lib/diaspora_federation/entities/profile_spec.rb b/spec/lib/diaspora_federation/entities/profile_spec.rb new file mode 100644 index 0000000..398f601 --- /dev/null +++ b/spec/lib/diaspora_federation/entities/profile_spec.rb @@ -0,0 +1,8 @@ +module DiasporaFederation + describe Entities::Profile do + let(:data) { FactoryGirl.attributes_for(:profile_entity) } + let(:klass) { Entities::Profile } + + it_behaves_like "an Entity subclass" + end +end diff --git a/spec/support/shared_entity_specs.rb b/spec/support/shared_entity_specs.rb new file mode 100644 index 0000000..8946cee --- /dev/null +++ b/spec/support/shared_entity_specs.rb @@ -0,0 +1,33 @@ +shared_examples "an Entity subclass" do + it "should be an Entity" do + expect(klass).to be < DiasporaFederation::Entity + end + + it "has its properties set" do + expect(klass.class_prop_names).to include(*data.keys) + end + + context "behaviour" do + let(:instance) { klass.new(data) } + + describe "initialize" do + it "must not create blank instances" do + expect { klass.new({}) }.to raise_error ArgumentError + end + + it "fails if nil was given" do + expect { klass.new(nil) }.to raise_error ArgumentError, "expected a Hash" + end + + it "should be frozen" do + expect(instance).to be_frozen + end + end + + describe "#to_h" do + it "should resemble the input data" do + expect(instance.to_h).to eq(data) + end + end + end +end