Merge branch 'discovery'
This commit is contained in:
commit
d5ffc61448
72 changed files with 2476 additions and 273 deletions
|
|
@ -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
|
||||
|
|
|
|||
1
Gemfile
1
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
|
||||
|
|
|
|||
22
Gemfile.lock
22
Gemfile.lock
|
|
@ -2,7 +2,11 @@ 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)
|
||||
valid (~> 0.5.0)
|
||||
diaspora_federation-rails (0.0.3)
|
||||
diaspora_federation (= 0.0.3)
|
||||
rails (~> 4.2)
|
||||
|
|
@ -45,6 +49,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 +61,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 +124,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 +207,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 +233,16 @@ 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)
|
||||
valid (0.5.0)
|
||||
webmock (1.21.0)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
yard (0.8.7.6)
|
||||
|
||||
PLATFORMS
|
||||
|
|
@ -250,6 +271,7 @@ DEPENDENCIES
|
|||
spring-watcher-listen
|
||||
sqlite3 (~> 1.3.10)
|
||||
uuid (~> 2.3.8)
|
||||
webmock (~> 1.21.0)
|
||||
yard
|
||||
|
||||
BUNDLED WITH
|
||||
|
|
|
|||
|
|
@ -43,10 +43,10 @@ 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::WebFinger::WebFinger.new(
|
||||
DiasporaFederation::Discovery::WebFinger.new(
|
||||
# ...
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -48,11 +50,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
|
||||
|
|
|
|||
|
|
@ -21,4 +21,8 @@ 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"
|
||||
s.add_dependency "valid", "~> 0.5.0"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,8 +3,13 @@ require "diaspora_federation/logging"
|
|||
require "diaspora_federation/callbacks"
|
||||
require "diaspora_federation/properties_dsl"
|
||||
require "diaspora_federation/entity"
|
||||
require "diaspora_federation/validators"
|
||||
|
||||
require "diaspora_federation/web_finger"
|
||||
require "diaspora_federation/fetcher"
|
||||
|
||||
require "diaspora_federation/entities"
|
||||
|
||||
require "diaspora_federation/discovery"
|
||||
|
||||
# diaspora* federation library
|
||||
module DiasporaFederation
|
||||
|
|
@ -30,6 +35,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 +74,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
|
||||
|
||||
|
|
|
|||
14
lib/diaspora_federation/discovery.rb
Normal file
14
lib/diaspora_federation/discovery.rb
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
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"
|
||||
require "diaspora_federation/discovery/discovery"
|
||||
91
lib/diaspora_federation/discovery/discovery.rb
Normal file
91
lib/diaspora_federation/discovery/discovery.rb
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
module DiasporaFederation
|
||||
module Discovery
|
||||
# This class contains the logic to fetch all data for the given diaspora ID
|
||||
class Discovery
|
||||
include DiasporaFederation::Logging
|
||||
|
||||
# @return [String] the diaspora ID of the account
|
||||
attr_reader :diaspora_id
|
||||
|
||||
# @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 #{diaspora_id}"
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
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 #{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 #{diaspora_id}: #{e.class}: #{e.message}"
|
||||
url.sub!("https://", "http://")
|
||||
retry
|
||||
else
|
||||
raise DiscoveryError, "Failed to fetch #{url} for #{diaspora_id}: #{e.class}: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
def host_meta_url
|
||||
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:#{diaspora_id}")
|
||||
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 person
|
||||
Entities::Person.new(
|
||||
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_id: diaspora_id,
|
||||
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
|
||||
end
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
module DiasporaFederation
|
||||
module WebFinger
|
||||
module Discovery
|
||||
# Raised, if the XML structure is invalid
|
||||
class InvalidDocument < RuntimeError
|
||||
end
|
||||
|
|
@ -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
|
||||
|
|
@ -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+.
|
||||
|
|
@ -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
|
||||
#
|
||||
|
|
@ -48,12 +48,12 @@ 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
|
||||
|
||||
# @!attribute [r] full_name
|
||||
# @return [String] display name of the user
|
||||
# @return [String] display name of the user
|
||||
property :full_name
|
||||
|
||||
# @!attribute [r] url
|
||||
|
|
@ -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)
|
||||
|
|
@ -163,8 +161,8 @@ module DiasporaFederation
|
|||
def self.from_html(html_string)
|
||||
doc = parse_html_and_validate(html_string)
|
||||
|
||||
data = {
|
||||
guid: content_from_doc(doc, :uid),
|
||||
new(
|
||||
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"],
|
||||
|
|
@ -172,15 +170,13 @@ 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: change me! ###################
|
||||
# TODO: remove first_name and last_name!
|
||||
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
|
||||
|
||||
private
|
||||
|
|
@ -288,6 +284,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
|
||||
|
|
@ -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
|
||||
|
|
@ -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?
|
||||
|
||||
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -172,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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
#
|
||||
|
|
@ -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
|
||||
12
lib/diaspora_federation/entities.rb
Normal file
12
lib/diaspora_federation/entities.rb
Normal file
|
|
@ -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"
|
||||
33
lib/diaspora_federation/entities/person.rb
Normal file
33
lib/diaspora_federation/entities/person.rb
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
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
|
||||
# @return [String] guid
|
||||
property :guid
|
||||
|
||||
# @!attribute [r] diaspora_id
|
||||
# The diaspora ID of the person
|
||||
# @return [String] diaspora ID
|
||||
property :diaspora_id, xml_name: :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
|
||||
54
lib/diaspora_federation/entities/profile.rb
Normal file
54
lib/diaspora_federation/entities/profile.rb
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
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
|
||||
# @see Person#diaspora_id
|
||||
# @return [String] diaspora ID
|
||||
property :diaspora_id, xml_name: :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
|
||||
|
|
@ -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
|
||||
|
|
@ -37,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 "<EntityName>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.
|
||||
#
|
||||
|
|
@ -50,16 +57,18 @@ 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
|
||||
validate
|
||||
end
|
||||
|
||||
# Returns a Hash representing this Entity (attributes => values)
|
||||
# @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
|
||||
|
||||
|
|
@ -76,16 +85,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
|
||||
|
|
@ -113,34 +118,61 @@ 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
|
||||
|
||||
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
|
||||
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
|
||||
end
|
||||
Nokogiri::XML::Element.new(self.class.entity_name, doc).tap do |root_element|
|
||||
self.class.class_props.each do |prop_def|
|
||||
add_property_to_xml(doc, prop_def, root_element)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
root_element
|
||||
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)
|
||||
node = Nokogiri::XML::Element.new(name.to_s, doc)
|
||||
data = send(name).to_s
|
||||
node.content = data unless data.empty?
|
||||
node
|
||||
def simple_node(doc, name, property)
|
||||
Nokogiri::XML::Element.new(name.to_s, doc).tap do |node|
|
||||
data = public_send(property).to_s
|
||||
node.content = data unless data.empty?
|
||||
end
|
||||
end
|
||||
|
||||
# Raised, if entity is not valid
|
||||
class ValidationError < RuntimeError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
42
lib/diaspora_federation/fetcher.rb
Normal file
42
lib/diaspora_federation/fetcher.rb
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
38
lib/diaspora_federation/validators.rb
Normal file
38
lib/diaspora_federation/validators.rb
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
require "validation"
|
||||
require "validation/rule/regular_expression"
|
||||
require "validation/rule/not_empty"
|
||||
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/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"
|
||||
|
||||
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 "<EntityName>Validator".
|
||||
module Validators
|
||||
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"
|
||||
30
lib/diaspora_federation/validators/h_card_validator.rb
Normal file
30
lib/diaspora_federation/validators/h_card_validator.rb
Normal file
|
|
@ -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
|
||||
# @{<full_name> ; <diaspora_id>}
|
||||
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
|
||||
18
lib/diaspora_federation/validators/person_validator.rb
Normal file
18
lib/diaspora_federation/validators/person_validator.rb
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
module DiasporaFederation
|
||||
module Validators
|
||||
# This validates a {Entities::Person}
|
||||
class PersonValidator < Validation::Validator
|
||||
include Validation
|
||||
|
||||
rule :guid, :guid
|
||||
|
||||
rule :diaspora_id, :diaspora_id
|
||||
|
||||
rule :url, %i(not_nil nilableURI)
|
||||
|
||||
rule :profile, :not_nil
|
||||
|
||||
rule :exported_key, :public_key
|
||||
end
|
||||
end
|
||||
end
|
||||
33
lib/diaspora_federation/validators/profile_validator.rb
Normal file
33
lib/diaspora_federation/validators/profile_validator.rb
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
module DiasporaFederation
|
||||
module Validators
|
||||
# This validates a {Entities::Profile}
|
||||
class ProfileValidator < Validation::Validator
|
||||
include Validation
|
||||
|
||||
rule :diaspora_id, :diaspora_id
|
||||
|
||||
# the name must not contain a semicolon because of mentions
|
||||
# @{<full_name> ; <diaspora_id>}
|
||||
rule :first_name, regular_expression: {regex: /\A[^;]{,32}\z/}
|
||||
rule :last_name, regular_expression: {regex: /\A[^;]{,32}\z/}
|
||||
|
||||
# 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
|
||||
38
lib/diaspora_federation/validators/rules/birthday.rb
Normal file
38
lib/diaspora_federation/validators/rules/birthday.rb
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
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
|
||||
|
||||
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
|
||||
|
||||
# This rule has no params
|
||||
# @return [Hash] params
|
||||
def params
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
38
lib/diaspora_federation/validators/rules/boolean.rb
Normal file
38
lib/diaspora_federation/validators/rules/boolean.rb
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
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?
|
||||
|
||||
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 [true, false].include? value
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# This rule has no params
|
||||
# @return [Hash] params
|
||||
def params
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
46
lib/diaspora_federation/validators/rules/diaspora_id.rb
Normal file
46
lib/diaspora_federation/validators/rules/diaspora_id.rb
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
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
|
||||
# The Regex for a valid diaspora ID
|
||||
DIASPORA_ID = 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
|
||||
# @return [Symbol] error key
|
||||
def error_key
|
||||
:diaspora_id
|
||||
end
|
||||
|
||||
# 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
|
||||
end
|
||||
end
|
||||
end
|
||||
28
lib/diaspora_federation/validators/rules/guid.rb
Normal file
28
lib/diaspora_federation/validators/rules/guid.rb
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
19
lib/diaspora_federation/validators/rules/nilable_uri.rb
Normal file
19
lib/diaspora_federation/validators/rules/nilable_uri.rb
Normal file
|
|
@ -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
|
||||
23
lib/diaspora_federation/validators/rules/not_nil.rb
Normal file
23
lib/diaspora_federation/validators/rules/not_nil.rb
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
33
lib/diaspora_federation/validators/rules/public_key.rb
Normal file
33
lib/diaspora_federation/validators/rules/public_key.rb
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
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
|
||||
|
||||
# Determines if value is a valid public key
|
||||
def valid_value?(value)
|
||||
!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
|
||||
# @return [Hash] params
|
||||
def params
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
33
lib/diaspora_federation/validators/rules/tag_count.rb
Normal file
33
lib/diaspora_federation/validators/rules/tag_count.rb
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
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
|
||||
# @option params [Fixnum] :maximum maximum allowed tag count
|
||||
def initialize(params)
|
||||
unless params.include?(:maximum) && params[:maximum].is_a?(Fixnum)
|
||||
raise ArgumentError, "A number has to be specified for :maximum"
|
||||
end
|
||||
|
||||
@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
|
||||
end
|
||||
end
|
||||
end
|
||||
20
lib/diaspora_federation/validators/web_finger_validator.rb
Normal file
20
lib/diaspora_federation/validators/web_finger_validator.rb
Normal file
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
command="bundle exec rake --trace"
|
||||
exec $command
|
||||
|
|
@ -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
|
||||
|
|
@ -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 "<Subject>acct:alice@localhost:3000</Subject>"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,22 +1,39 @@
|
|||
module Entities
|
||||
class TestEntity < DiasporaFederation::Entity
|
||||
property :test
|
||||
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 OtherEntity < DiasporaFederation::Entity
|
||||
property :asdf
|
||||
end
|
||||
|
||||
class TestNestedEntity < DiasporaFederation::Entity
|
||||
property :asdf
|
||||
entity :test, TestEntity
|
||||
entity :multi, [OtherEntity]
|
||||
end
|
||||
|
||||
class TestEntityWithXmlName < DiasporaFederation::Entity
|
||||
property :test
|
||||
property :qwer, xml_name: :asdf
|
||||
end
|
||||
end
|
||||
|
||||
class TestDefaultEntity < DiasporaFederation::Entity
|
||||
property :test1
|
||||
property :test2
|
||||
property :test3, default: true
|
||||
property :test4, default: -> { true }
|
||||
end
|
||||
module Validators
|
||||
class TestDefaultEntityValidator < Validation::Validator
|
||||
include Validation
|
||||
|
||||
class OtherEntity < DiasporaFederation::Entity
|
||||
property :asdf
|
||||
end
|
||||
|
||||
class TestNestedEntity < DiasporaFederation::Entity
|
||||
property :asdf
|
||||
entity :test, TestEntity
|
||||
entity :multi, [OtherEntity]
|
||||
rule :test1, regular_expression: {regex: /\A[^;]{,32}\z/}
|
||||
rule :test2, :not_nil
|
||||
rule :test3, :boolean
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,12 +5,69 @@ 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 }
|
||||
|
||||
factory :person do
|
||||
sequence(:diaspora_handle) {|n| "person-#{n}-#{r_str}@localhost:3000" }
|
||||
diaspora_id
|
||||
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 :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
|
||||
diaspora_id
|
||||
url "http://localhost:3000/"
|
||||
exported_key { generate(:public_key) }
|
||||
profile {
|
||||
DiasporaFederation::Entities::Profile.new(
|
||||
FactoryGirl.attributes_for(:profile_entity, diaspora_id: diaspora_id))
|
||||
}
|
||||
end
|
||||
|
||||
factory :profile_entity, class: DiasporaFederation::Entities::Profile do
|
||||
diaspora_id
|
||||
first_name "my name"
|
||||
last_name nil
|
||||
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
|
||||
|
|
|
|||
192
spec/lib/diaspora_federation/discovery/discovery_spec.rb
Normal file
192
spec/lib/diaspora_federation/discovery/discovery_spec.rb
Normal file
|
|
@ -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_id }
|
||||
let(:default_image) { "http://localhost:3000/assets/user/default.png" }
|
||||
|
||||
describe "#intialize" do
|
||||
it "sets diaspora id" do
|
||||
discovery = Discovery::Discovery.new("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.diaspora_id).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_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_id).to eq(alice.diaspora_id)
|
||||
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_id).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_id).to eq(account)
|
||||
end
|
||||
|
||||
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")
|
||||
.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 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}")
|
||||
.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
|
||||
<div id="content">
|
||||
<h1>#{account}</h1>
|
||||
<div id="content_inner">
|
||||
<div class="entity_profile vcard author" id="i">
|
||||
<h2>User profile</h2>
|
||||
<dl class="entity_nickname">
|
||||
<dt>Nickname</dt>
|
||||
<dd>
|
||||
<a class="nickname url uid" href="#{alice.url}" rel="me"></a>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="entity_given_name">
|
||||
<dt>First name</dt>
|
||||
<dd>
|
||||
<span class="given_name"></span>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="entity_family_name">
|
||||
<dt>Family name</dt>
|
||||
<dd>
|
||||
<span class="family_name"></span>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="entity_fn">
|
||||
<dt>Full name</dt>
|
||||
<dd>
|
||||
<span class="fn"></span>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="entity_url">
|
||||
<dt>URL</dt>
|
||||
<dd>
|
||||
<a class="url" href="#{alice.url}" id="pod_location" rel="me">#{alice.url}</a>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="entity_photo">
|
||||
<dt>Photo</dt>
|
||||
<dd>
|
||||
<img class="photo avatar" height="300px" src="#{default_image}" width="300px">
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="entity_photo_medium">
|
||||
<dt>Photo</dt>
|
||||
<dd>
|
||||
<img class="photo avatar" height="100px" src="#{default_image}" width="100px">
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="entity_photo_small">
|
||||
<dt>Photo</dt>
|
||||
<dd>
|
||||
<img class="photo avatar" height="50px" src="#{default_image}" width="50px">
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="entity_searchable">
|
||||
<dt>Searchable</dt>
|
||||
<dd>
|
||||
<span class="searchable">true</span>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
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_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_id).to eq(alice.diaspora_id)
|
||||
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
|
||||
|
|
@ -1,10 +1,27 @@
|
|||
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" }
|
||||
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
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
|
|
@ -92,36 +109,18 @@ module DiasporaFederation
|
|||
HTML
|
||||
}
|
||||
|
||||
it "must not create blank instances" do
|
||||
expect { WebFinger::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 = WebFinger::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 { WebFinger::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 +136,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,11 +146,30 @@ HTML
|
|||
"class=\"searchable\"><"
|
||||
)
|
||||
|
||||
hcard = WebFinger::HCard.from_html(changed_html)
|
||||
hcard = Discovery::HCard.from_html(changed_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
|
||||
<div id="content">
|
||||
|
|
@ -218,7 +236,7 @@ HTML
|
|||
</div>
|
||||
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)
|
||||
|
|
@ -227,6 +245,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
|
||||
|
|
@ -235,11 +256,11 @@ HTML
|
|||
<span class="fn">#{person.full_name}</span>
|
||||
</div>
|
||||
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
|
||||
|
|
@ -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,33 @@ 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 "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 = 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 +54,7 @@ XML
|
|||
|
||||
</XRD>
|
||||
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 +64,7 @@ XML
|
|||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
|
||||
</XRD>
|
||||
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 +74,11 @@ XML
|
|||
<Link rel="lrdd" type="application/xrd+xml" template="#{base_url}webfinger?q="/>
|
||||
</XRD>
|
||||
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
|
||||
|
|
@ -1,9 +1,24 @@
|
|||
module DiasporaFederation
|
||||
describe WebFinger::WebFinger do
|
||||
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(: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
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
|
@ -21,34 +36,18 @@ module DiasporaFederation
|
|||
XML
|
||||
}
|
||||
|
||||
it "must not create blank instances" do
|
||||
expect { WebFinger::WebFinger.new({}) }.to raise_error ArgumentError
|
||||
end
|
||||
it_behaves_like "an Entity subclass"
|
||||
|
||||
context "generation" do
|
||||
it "creates a nice XML document" do
|
||||
wf = WebFinger::WebFinger.new(
|
||||
acct_uri: "acct:#{person.diaspora_handle}",
|
||||
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 { WebFinger::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 +61,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 +83,7 @@ XML
|
|||
</XRD>
|
||||
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)
|
||||
|
|
@ -97,17 +96,44 @@ 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
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
|
||||
<Subject>#{acct}</Subject>
|
||||
<Alias>#{person.alias_url}</Alias>
|
||||
<Link rel="http://microformats.org/profile/hcard" type="text/html" href="#{person.hcard_url}"/>
|
||||
<Link rel="http://joindiaspora.com/seed_location" type="text/html" href="#{person.url}"/>
|
||||
<Link rel="http://webfinger.net/rel/profile-page" type="text/html" href="#{person.profile_url}"/>
|
||||
<Link rel="http://schemas.google.com/g/2010#updates-from" type="application/atom+xml" href="#{person.atom_url}"/>
|
||||
<Link rel="salmon" href="#{person.salmon_url}"/>
|
||||
</XRD>
|
||||
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 = <<XML
|
||||
invalid_xml = <<-XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
|
||||
</XRD>
|
||||
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
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
module DiasporaFederation
|
||||
describe WebFinger::XrdDocument do
|
||||
describe Discovery::XrdDocument do
|
||||
let(:xml) {
|
||||
<<XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
|
@ -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("<html></html>") }.to raise_error WebFinger::InvalidDocument
|
||||
expect { Discovery::XrdDocument.xml_data("<html></html>") }.to raise_error Discovery::InvalidDocument
|
||||
end
|
||||
end
|
||||
end
|
||||
8
spec/lib/diaspora_federation/entities/person_spec.rb
Normal file
8
spec/lib/diaspora_federation/entities/person_spec.rb
Normal file
|
|
@ -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
|
||||
8
spec/lib/diaspora_federation/entities/profile_spec.rb
Normal file
8
spec/lib/diaspora_federation/entities/profile_spec.rb
Normal file
|
|
@ -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
|
||||
|
|
@ -18,19 +18,50 @@ 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.to_h[:test3]).to be_truthy
|
||||
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 "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 "handles callable defaults" do
|
||||
entity = Entities::TestDefaultEntity.new(test1: 1, test2: 2)
|
||||
expect(entity.to_h[:test4]).to be_truthy
|
||||
end
|
||||
|
||||
it "uses provided values over defaults" do
|
||||
it "sets nil if string is empty" do
|
||||
data[:test1] = ""
|
||||
entity = Entities::TestDefaultEntity.new(data)
|
||||
expect(entity.to_h[:test3]).to be_falsey
|
||||
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
|
||||
|
||||
|
|
@ -54,7 +85,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
|
||||
|
|
@ -85,6 +118,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
|
||||
|
|
@ -92,5 +126,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
|
||||
|
|
|
|||
56
spec/lib/diaspora_federation/fetcher_spec.rb
Normal file
56
spec/lib/diaspora_federation/fetcher_spec.rb
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
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)
|
||||
|
||||
expect(validator).to be_valid
|
||||
expect(validator.errors).to be_empty
|
||||
end
|
||||
|
||||
it_behaves_like "a diaspora id validator" do
|
||||
let(:property) { :diaspora_id }
|
||||
end
|
||||
|
||||
it_behaves_like "a guid validator" do
|
||||
let(:property) { :guid }
|
||||
end
|
||||
|
||||
context "#url" do
|
||||
it_behaves_like "a url validator without path" do
|
||||
let(:property) { :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
|
||||
|
||||
it_behaves_like "a public key validator" do
|
||||
let(:property) { :exported_key }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
module DiasporaFederation
|
||||
describe Validators::ProfileValidator do
|
||||
let(:entity) { :profile_entity }
|
||||
|
||||
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 id validator" do
|
||||
let(:property) { :diaspora_id }
|
||||
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(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).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
|
||||
|
||||
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(:property) { 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
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
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
|
||||
|
||||
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))
|
||||
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
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
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
|
||||
|
||||
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
|
||||
%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
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
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
|
||||
|
||||
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"))
|
||||
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
|
||||
63
spec/lib/diaspora_federation/validators/rules/guid_spec.rb
Normal file
63
spec/lib/diaspora_federation/validators/rules/guid_spec.rb
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
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
|
||||
|
||||
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"))
|
||||
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
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
describe Validation::Rule::NilableURI do
|
||||
it "has an error key" do
|
||||
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, :nilableURI)
|
||||
|
||||
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, :nilableURI)
|
||||
|
||||
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, :nilableURI)
|
||||
|
||||
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, :nilableURI)
|
||||
|
||||
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, :nilableURI)
|
||||
|
||||
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, nilableURI: %i(host path))
|
||||
|
||||
expect(validator).not_to be_valid
|
||||
expect(validator.errors).to include(:uri)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
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
|
||||
|
||||
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"))
|
||||
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
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
describe Validation::Rule::PublicKey do
|
||||
it "will not accept parameters" do
|
||||
validator = Validation::Validator.new({})
|
||||
expect {
|
||||
validator.rule(:key, public_key: {param: true})
|
||||
}.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
|
||||
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
|
||||
|
||||
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
|
||||
end
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
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
|
||||
|
||||
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
|
||||
|
||||
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" }
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -18,6 +19,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"
|
||||
|
|
@ -28,12 +30,9 @@ 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
|
||||
|
||||
# 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"
|
||||
|
|
@ -53,6 +52,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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
33
spec/support/shared_entity_specs.rb
Normal file
33
spec/support/shared_entity_specs.rb
Normal file
|
|
@ -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
|
||||
211
spec/support/shared_validator_specs.rb
Normal file
211
spec/support/shared_validator_specs.rb
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
def entity_stub(entity, property, val)
|
||||
instance = OpenStruct.new(FactoryGirl.attributes_for(entity))
|
||||
instance.public_send("#{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|
|
||||
validator = described_class.new(entity_stub(entity, property, val))
|
||||
|
||||
expect(validator).not_to be_valid
|
||||
expect(validator.errors).to include(property)
|
||||
end
|
||||
end
|
||||
|
||||
it "must be a valid diaspora id" do
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples "a guid validator" do
|
||||
it "validates a well-formed guid from redmatrix" do
|
||||
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 = 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 = 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 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 boolean validator" do
|
||||
it "validates a well-formed boolean" do
|
||||
[true, "true", false, "false"].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 "must not be an arbitrary string or other object" do
|
||||
["asdf", Time.zone.today, 1234].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 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 "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
|
||||
|
||||
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 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|
|
||||
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"))
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,26 @@
|
|||
require "diaspora_federation/web_finger"
|
||||
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)
|
||||
on :person_webfinger_fetch do |diaspora_id|
|
||||
person = Person.find_by(diaspora_id: diaspora_id)
|
||||
if person
|
||||
DiasporaFederation::WebFinger::WebFinger.new(
|
||||
acct_uri: "acct:#{person.diaspora_handle}",
|
||||
DiasporaFederation::Discovery::WebFinger.new(
|
||||
acct_uri: "acct:#{person.diaspora_id}",
|
||||
alias_url: person.alias_url,
|
||||
hcard_url: person.hcard_url,
|
||||
seed_url: person.url,
|
||||
|
|
@ -26,7 +36,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,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
class RenameDiasporaHandleToDiasporaId < ActiveRecord::Migration
|
||||
def change
|
||||
rename_column :people, :diaspora_handle, :diaspora_id
|
||||
end
|
||||
end
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue