diaspora_federation-0.0.8/0000755000004100000410000000000012601401764015552 5ustar www-datawww-datadiaspora_federation-0.0.8/lib/0000755000004100000410000000000012601401764016320 5ustar www-datawww-datadiaspora_federation-0.0.8/lib/diaspora_federation/0000755000004100000410000000000012601401764022322 5ustar www-datawww-datadiaspora_federation-0.0.8/lib/diaspora_federation/entity.rb0000644000004100000410000001401412601401764024163 0ustar www-datawww-datamodule DiasporaFederation # +Entity+ is the base class for all other objects used to encapsulate data # for federation messages in the Diaspora* network. # Entity fields are specified using a simple {PropertiesDSL DSL} as part of # the class definition. # # Any entity also provides the means to serialize itself and all nested # entities to XML (for deserialization from XML to +Entity+ instances, see # {XmlPayload}). # # @abstract Subclass and specify properties to implement various entities. # # @example Entity subclass definition # class MyEntity < Entity # 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 # # @example Entity instantiation # nentity = NestedEntity.new # oe1 = OtherEntity.new # oe2 = OtherEntity.new # # entity = MyEntity.new(prop: 'some property', # nested: nentity, # multiple: [oe1, oe2]) # # @note Entity properties can only be set during initialization, after that the # entity instance becomes frozen and must not be modified anymore. Instances # are intended to be immutable data containers, only. class Entity extend PropertiesDSL # Initializes the Entity with the given attribute hash and freezes the created # instance it returns. # # After creation, the entity is validated against a Validator, if one is defined. # The Validator needs to be in the {DiasporaFederation::Validators} namespace and # named like "Validator". Only valid entities can be created. # # @see DiasporaFederation::Validators # # @note Attributes not defined as part of the class definition ({PropertiesDSL#property}, # {PropertiesDSL#entity}) get discarded silently. # # @param [Hash] data # @return [Entity] new instance def initialize(data) raise ArgumentError, "expected a Hash" unless data.is_a?(Hash) missing_props = self.class.missing_props(data) unless missing_props.empty? raise ArgumentError, "missing required properties: #{missing_props.join(', ')}" end self.class.default_values.merge(data).each do |k, v| instance_variable_set("@#{k}", nilify(v)) if setable?(k, v) end freeze validate end # Returns a Hash representing this Entity (attributes => values) # @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] = public_send(prop) end end # Returns the XML representation for this entity constructed out of # {http://www.rubydoc.info/gems/nokogiri/Nokogiri/XML/Element Nokogiri::XML::Element}s # # @see Nokogiri::XML::Node.to_xml # @see XmlPayload.pack # # @return [Nokogiri::XML::Element] root element containing properties as child elements def to_xml entity_xml end # some of this is from Rails "Inflector.demodulize" and "Inflector.undersore" def self.entity_name 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 def setable?(name, val) prop_def = self.class.class_props.find {|p| p[:name] == name } return false if prop_def.nil? # property undefined setable_string?(prop_def, val) || setable_nested?(prop_def, val) || setable_multi?(prop_def, val) end def setable_string?(definition, val) (definition[:type] == String && val.respond_to?(:to_s)) end def setable_nested?(definition, val) t = definition[:type] (t.is_a?(Class) && t.ancestors.include?(Entity) && val.is_a?(Entity)) end def setable_multi?(definition, val) t = definition[:type] (t.instance_of?(Array) && val.instance_of?(Array) && 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 = "#{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) 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 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, 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 diaspora_federation-0.0.8/lib/diaspora_federation/entities/0000755000004100000410000000000012601401764024146 5ustar www-datawww-datadiaspora_federation-0.0.8/lib/diaspora_federation/entities/profile.rb0000644000004100000410000000325612601401764026141 0ustar www-datawww-datamodule 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 diaspora_federation-0.0.8/lib/diaspora_federation/entities/person.rb0000644000004100000410000000160212601401764026000 0ustar www-datawww-datamodule 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 diaspora_federation-0.0.8/lib/diaspora_federation/discovery/0000755000004100000410000000000012601401764024331 5ustar www-datawww-datadiaspora_federation-0.0.8/lib/diaspora_federation/discovery/h_card.rb0000644000004100000410000002662512601401764026111 0ustar www-datawww-datamodule DiasporaFederation 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+. # There is a draft for a new h-card format specification, that makes use of # the new vCard standard. # # @note The current implementation contains a huge amount of legacy elements # and classes, that should be removed and cleaned up in later iterations. # # @todo This needs some radical restructuring. The generated HTML is not # correctly nested according to the hCard standard and class names are # partially wrong. Also, apart from that, it's just ugly. # # @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", # public_key: "-----BEGIN PUBLIC KEY-----\nABCDEF==\n-----END PUBLIC KEY-----", # searchable: true, # first_name: "User", # last_name: "Name" # ) # html_string = hc.to_html # # @example Create a HCard instance from an hCard document # hc = HCard.from_html(html_string) # ... # full_name = hc.full_name # ... # # @see http://microformats.org/wiki/hCard "hCard 1.0" # @see http://microformats.org/wiki/h-card "h-card" (draft) # @see http://www.ietf.org/rfc/rfc2426.txt "vCard MIME Directory Profile" (obsolete) # @see http://www.ietf.org/rfc/rfc6350.txt "vCard Format Specification" class HCard < Entity # @!attribute [r] guid # This is just the guid. When a user creates an account on a pod, the pod # MUST assign them a guid - a random hexadecimal string of at least 8 # hexadecimal digits. # @return [String] guid property :guid # @!attribute [r] nickname # the first part of the diaspora ID # @return [String] nickname property :nickname # @!attribute [r] full_name # @return [String] display name of the user property :full_name # @!attribute [r] url # @deprecated should be changed to the profile url. The pod url is in # the WebFinger (see {WebFinger#seed_url}, will affect older Diaspora* # installations). # # @return [String] link to the pod property :url # @!attribute [r] public_key # When a user is created on the pod, the pod MUST generate a pgp keypair # for them. This key is used for signing messages. The format is a # DER-encoded PKCS#1 key beginning with the text # "-----BEGIN PUBLIC KEY-----" and ending with "-----END PUBLIC KEY-----". # # @return [String] public key property :public_key # @!attribute [r] photo_large_url # @return [String] url to the big avatar (300x300) property :photo_large_url # @!attribute [r] photo_medium_url # @return [String] url to the medium avatar (100x100) property :photo_medium_url # @!attribute [r] photo_small_url # @return [String] url to the small avatar (50x50) property :photo_small_url # @!attribute [r] first_name # @deprecated We decided to only use one name field, these should be removed # in later iterations (will affect older Diaspora* installations). # # @see #full_name # @return [String] first name property :first_name # @!attribute [r] last_name # @deprecated We decided to only use one name field, these should be removed # in later iterations (will affect older Diaspora* installations). # # @see #full_name # @return [String] last name property :last_name # @!attribute [r] searchable # @deprecated As this is a simple property, consider move to WebFinger instead # of HCard. vCard has no comparable field for this information, but # Webfinger may declare arbitrary properties (will affect older Diaspora* # installations). # # flag if a user is searchable by name # @return [Boolean] searchable flag property :searchable # CSS selectors for finding all the hCard fields SELECTORS = { uid: ".uid", nickname: ".nickname", fn: ".fn", given_name: ".given_name", family_name: ".family_name", url: "#pod_location[href]", photo: ".entity_photo .photo[src]", photo_medium: ".entity_photo_medium .photo[src]", photo_small: ".entity_photo_small .photo[src]", key: ".key", searchable: ".searchable" } # Create the HTML string from the current HCard instance # @return [String] HTML string def to_html builder = create_builder content = builder.doc.at_css("#content_inner") add_simple_property(content, :uid, "uid", @guid) add_simple_property(content, :nickname, "nickname", @nickname) add_simple_property(content, :full_name, "fn", @full_name) add_simple_property(content, :searchable, "searchable", @searchable) add_property(content, :key) do |html| html.pre(@public_key.to_s, class: "key") end # TODO: remove me! ################### add_simple_property(content, :first_name, "given_name", @first_name) add_simple_property(content, :family_name, "family_name", @last_name) ####################################### add_property(content, :url) do |html| html.a(@url.to_s, id: "pod_location", class: "url", rel: "me", href: @url.to_s) end add_photos(content) builder.doc.to_xhtml(indent: 2, indent_text: " ") end # Creates a new HCard instance from the given HTML string. # @param html_string [String] HTML string # @return [HCard] HCard instance # @raise [InvalidData] if the HTML string is invalid or incomplete def self.from_html(html_string) doc = parse_html_and_validate(html_string) 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"], photo_large_url: photo_from_doc(doc, :photo), photo_medium_url: photo_from_doc(doc, :photo_medium), photo_small_url: photo_from_doc(doc, :photo_small), searchable: (content_from_doc(doc, :searchable) == "true"), # TODO: public key is new and can be missing public_key: (content_from_doc(doc, :key) unless element_from_doc(doc, :key).nil?), # TODO: remove first_name and last_name! first_name: content_from_doc(doc, :given_name), last_name: content_from_doc(doc, :family_name) ) end private # Creates the base HCard html structure # @return [Nokogiri::HTML::Builder] HTML Builder instance def create_builder Nokogiri::HTML::Builder.new do |html| html.html { html.head { html.meta(charset: "UTF-8") html.title(@full_name) } html.body { html.div(id: "content") { html.h1(@full_name) html.div(id: "content_inner", class: "entity_profile vcard author") { html.h2("User profile") } } } } end end # Add a property to the hCard document. The element will be added to the given # container element and a "definition list" structure will be created around # it. A Nokogiri::HTML::Builder instance will be passed to the given block, # which should be used to add the element(s) containing the property data. # # @param container [Nokogiri::XML::Element] parent element for added property HTML # @param name [Symbol] property name # @param block [Proc] block returning an element def add_property(container, name, &block) Nokogiri::HTML::Builder.with(container) do |html| html.dl(class: "entity_#{name}") { html.dt(name.to_s.capitalize) html.dd { block.call(html) } } end end # Calls {HCard#add_property} for a simple text property. # @param container [Nokogiri::XML::Element] parent element # @param name [Symbol] property name # @param class_name [String] HTML class name # @param value [#to_s] property value # @see HCard#add_property def add_simple_property(container, name, class_name, value) add_property(container, name) do |html| html.span(value.to_s, class: class_name) end end # Calls {HCard#add_property} to add the photos # @param container [Nokogiri::XML::Element] parent element # @see HCard#add_property def add_photos(container) add_property(container, :photo) do |html| html.img(class: "photo avatar", width: "300", height: "300", src: @photo_large_url.to_s) end add_property(container, :photo_medium) do |html| html.img(class: "photo avatar", width: "100", height: "100", src: @photo_medium_url.to_s) end add_property(container, :photo_small) do |html| html.img(class: "photo avatar", width: "50", height: "50", src: @photo_small_url.to_s) end end # Make sure some of the most important elements are present in the parsed # HTML document. # @param [LibXML::XML::Document] doc HTML document # @return [Boolean] validation result def self.html_document_complete?(doc) !(doc.at_css(SELECTORS[:fn]).nil? || doc.at_css(SELECTORS[:nickname]).nil? || doc.at_css(SELECTORS[:url]).nil? || doc.at_css(SELECTORS[:photo]).nil?) end private_class_method :html_document_complete? def self.parse_html_and_validate(html_string) raise ArgumentError, "hcard html is not a string" unless html_string.instance_of?(String) doc = Nokogiri::HTML::Document.parse(html_string) raise InvalidData, "hcard html incomplete" unless html_document_complete?(doc) doc end private_class_method :parse_html_and_validate def self.element_from_doc(doc, selector) doc.at_css(SELECTORS[selector]) end private_class_method :element_from_doc def self.content_from_doc(doc, content_selector) element_from_doc(doc, content_selector).content end private_class_method :content_from_doc def self.photo_from_doc(doc, photo_selector) 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 diaspora_federation-0.0.8/lib/diaspora_federation/discovery/host_meta.rb0000644000004100000410000000661112601401764026645 0ustar www-datawww-data module DiasporaFederation module Discovery # Generates and parses Host Meta documents. # # This is a minimal implementation of the standard, only to the degree of what # is used for the purposes of the Diaspora* protocol. (e.g. WebFinger) # # @example Creating a Host Meta document # doc = HostMeta.from_base_url("https://pod.example.tld/") # doc.to_xml # # @example Parsing a Host Meta document # doc = HostMeta.from_xml(xml_string) # webfinger_tpl = doc.webfinger_template_url # # @see http://tools.ietf.org/html/rfc6415 RFC 6415: "Web Host Metadata" # @see XrdDocument class HostMeta private_class_method :new # create a new host-meta instance # @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}" # Returns the WebFinger URL that was used to build this instance (either from # xml or by giving a base URL). # @return [String] WebFinger template URL def webfinger_template_url @webfinger_url end # Produces the XML string for the Host Meta instance with a +Link+ element # containing the +webfinger_url+. # @return [String] XML string def to_xml doc = XrdDocument.new doc.links << {rel: "lrdd", type: "application/xrd+xml", template: @webfinger_url} doc.to_xml end # 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) webfinger_url = "#{base_url.to_s.chomp('/')}#{WEBFINGER_SUFFIX}" raise InvalidData, "invalid webfinger url: #{webfinger_url}" unless webfinger_url_valid?(webfinger_url) new(webfinger_url) end # Reads the given Host Meta XML document string and populates the # +webfinger_url+. # @param [String] hostmeta_xml Host Meta XML string # @raise [InvalidData] if the xml or the webfinger url is malformed def self.from_xml(hostmeta_xml) data = XrdDocument.xml_data(hostmeta_xml) raise InvalidData, "received an invalid xml" unless data.key?(:links) webfinger_url = webfinger_url_from_xrd(data) raise InvalidData, "invalid webfinger url: #{webfinger_url}" unless webfinger_url_valid?(webfinger_url) 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 end private_class_method :webfinger_url_valid? # Gets the webfinger url from an XRD data structure # @param [Hash] data extracted data # @return [String] webfinger url def self.webfinger_url_from_xrd(data) link = data[:links].find {|l| l[:rel] == "lrdd" } return link[:template] unless link.nil? end private_class_method :webfinger_url_from_xrd end end end diaspora_federation-0.0.8/lib/diaspora_federation/discovery/web_finger.rb0000644000004100000410000002217612601401764026775 0ustar www-datawww-datamodule DiasporaFederation 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} # (from around 2010). # # In the meantime an actual RFC draft has been in development, which should # serve as a base for all future changes of this implementation. # # @example Creating a WebFinger document from a person hash # wf = WebFinger.new( # acct_uri: "acct:user@server.example", # alias_url: "https://server.example/people/0123456789abcdef", # hcard_url: "https://server.example/hcard/users/user", # seed_url: "https://server.example/", # profile_url: "https://server.example/u/user", # atom_url: "https://server.example/public/user.atom", # salmon_url: "https://server.example/receive/users/0123456789abcdef", # guid: "0123456789abcdef", # public_key: "-----BEGIN PUBLIC KEY-----\nABCDEF==\n-----END PUBLIC KEY-----" # ) # xml_string = wf.to_xml # # @example Creating a WebFinger instance from an xml document # wf = WebFinger.from_xml(xml_string) # ... # hcard_url = wf.hcard_url # ... # # @see http://tools.ietf.org/html/draft-jones-appsawg-webfinger "WebFinger" - # current draft # @see http://code.google.com/p/webfinger/wiki/CommonLinkRelations # @see http://www.iana.org/assignments/link-relations/link-relations.xhtml # official list of IANA link relations class WebFinger < Entity # @!attribute [r] acct_uri # The Subject element should contain the webfinger address that was asked # for. If it does not, then this webfinger profile MUST be ignored. # @return [String] property :acct_uri # @!attribute [r] alias_url # @note could be nil # @return [String] link to the users profile property :alias_url # @!attribute [r] hcard_url # @return [String] link to the +hCard+ property :hcard_url # @!attribute [r] seed_url # @return [String] link to the pod property :seed_url # @!attribute [r] profile_url # @return [String] link to the users profile property :profile_url # @!attribute [r] atom_url # This atom feed is an Activity Stream of the user's public posts. Diaspora # pods SHOULD publish an Activity Stream of public posts, but there is # currently no requirement to be able to read Activity Streams. # @see http://activitystrea.ms/ Activity Streams specification # # Note that this feed MAY also be made available through the PubSubHubbub # mechanism by supplying a in the atom feed itself. # @return [String] atom feed url property :atom_url # @!attribute [r] salmon_url # @note could be nil # @return [String] salmon endpoint url # @see http://salmon-protocol.googlecode.com/svn/trunk/draft-panzer-salmon-00.html#SMLR # Panzer draft for Salmon, paragraph 3.3 property :salmon_url # @!attribute [r] guid # @deprecated Either convert these to +Property+ elements or move to the # +hCard+, which actually has fields for an +UID+ defined in the +vCard+ # specification (will affect older Diaspora* installations). # # @see HCard#guid # # This is just the guid. When a user creates an account on a pod, the pod # MUST assign them a guid - a random hexadecimal string of at least 8 # hexadecimal digits. # @return [String] guid property :guid # @!attribute [r] public_key # @deprecated Either convert these to +Property+ elements or move to the # +hCard+, which actually has fields for an +KEY+ defined in the +vCard+ # specification (will affect older Diaspora* installations). # # @see HCard#pubkey # # When a user is created on the pod, the pod MUST generate a pgp keypair # for them. This key is used for signing messages. The format is a # DER-encoded PKCS#1 key beginning with the text # "-----BEGIN PUBLIC KEY-----" and ending with "-----END PUBLIC KEY-----". # @return [String] public key property :public_key # +hcard_url+ link relation REL_HCARD = "http://microformats.org/profile/hcard" # +seed_url+ link relation REL_SEED = "http://joindiaspora.com/seed_location" # @deprecated This should be a +Property+ or moved to the +hCard+, but +Link+ # is inappropriate according to the specification (will affect older # Diaspora* installations). # +guid+ link relation REL_GUID = "http://joindiaspora.com/guid" # +profile_url+ link relation. # @note This might just as well be an +Alias+ instead of a +Link+. REL_PROFILE = "http://webfinger.net/rel/profile-page" # +atom_url+ link relation REL_ATOM = "http://schemas.google.com/g/2010#updates-from" # +salmon_url+ link relation REL_SALMON = "salmon" # @deprecated This should be a +Property+ or moved to the +hcard+, but +Link+ # is inappropriate according to the specification (will affect older # Diaspora* installations). # +pubkey+ link relation REL_PUBKEY = "diaspora-public-key" # Create the XML string from the current WebFinger instance # @return [String] XML string def to_xml doc = XrdDocument.new doc.subject = @acct_uri doc.aliases << @alias_url add_links_to(doc) doc.to_xml end # Create a WebFinger instance from the given XML string. # @param [String] webfinger_xml WebFinger XML string # @return [WebFinger] WebFinger instance # @raise [InvalidData] if the given XML string is invalid or incomplete def self.from_xml(webfinger_xml) data = parse_xml_and_validate(webfinger_xml) 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: parse_alias(data[:aliases]), 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: parse_link(links, REL_GUID), public_key: (Base64.strict_decode64(public_key) if public_key) ) end private # Parses the XML string to a Hash and does some rudimentary checking on # the data Hash. # @param [String] webfinger_xml WebFinger XML string # @return [Hash] data XML data # @raise [InvalidData] if the given XML string is invalid or incomplete def self.parse_xml_and_validate(webfinger_xml) XrdDocument.xml_data(webfinger_xml).tap do |data| valid = data.key?(:subject) && data.key?(:links) raise InvalidData, "webfinger xml is incomplete" unless valid end end private_class_method :parse_xml_and_validate def add_links_to(doc) doc.links << {rel: REL_HCARD, type: "text/html", href: @hcard_url} doc.links << {rel: REL_SEED, type: "text/html", href: @seed_url} # TODO: remove me! ############## doc.links << {rel: REL_GUID, type: "text/html", href: @guid} ################################## doc.links << {rel: REL_PROFILE, type: "text/html", href: @profile_url} doc.links << {rel: REL_ATOM, type: "application/atom+xml", href: @atom_url} doc.links << {rel: REL_SALMON, href: @salmon_url} # TODO: remove me! ############## doc.links << {rel: REL_PUBKEY, type: "RSA", href: Base64.strict_encode64(@public_key)} ################################## end def self.parse_link(links, rel) element = links.find {|l| l[:rel] == rel } element ? element[:href] : nil end private_class_method :parse_link # this method is used to parse the alias_url from the XML. # * redmatrix has sometimes no alias, return nil # * old pods had quotes around the alias url, this can be removed later # * friendica has two aliases and the first is with "acct:": return only an URL starting with http (or https) def self.parse_alias(aliases) return nil unless aliases # TODO: Old pods had quotes around alias. Remove the +map+ in next line, when all pods use this gem aliases.map {|a| a.gsub(/\A"|"\Z/, "") }.find {|a| a.start_with?("http") } end private_class_method :parse_alias end end end diaspora_federation-0.0.8/lib/diaspora_federation/discovery/discovery.rb0000644000004100000410000000633412601401764026673 0ustar www-datawww-datamodule 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 # create a discovery class for the 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 and saves it via callback # @return [Person] def fetch_and_save logger.info "Fetch data for #{diaspora_id}" validate_diaspora_id DiasporaFederation.callbacks.trigger(:save_person_after_webfinger, person) logger.info "successfully webfingered #{diaspora_id}" person end private def validate_diaspora_id # validates if the diaspora ID matches the diaspora ID in the webfinger response return if 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 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 @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 diaspora_federation-0.0.8/lib/diaspora_federation/discovery/xrd_document.rb0000644000004100000410000001421712601401764027356 0ustar www-datawww-datamodule DiasporaFederation 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. # # @note {http://tools.ietf.org/html/rfc6415 RFC 6415} recommends that servers # should also offer the JRD format in addition to the XRD representation. # Implementing +XrdDocument#to_json+ and +XrdDocument.json_data+ should # be almost trivial due to the simplicity of the format and the way the data # is stored internally already. See # {http://tools.ietf.org/html/rfc6415#appendix-A RFC 6415, Appendix A} # for a description of the JSON format. # # @example Creating a XrdDocument # doc = XrdDocument.new # doc.expires = DateTime.new(2020, 1, 15, 0, 0, 1) # doc.subject = "http://example.tld/articles/11" # doc.aliases << "http://example.tld/cool_article" # doc.aliases << "http://example.tld/authors/2/articles/3" # doc.properties["http://x.example.tld/ns/version"] = "1.3" # doc.links << { rel: "author", type: "text/html", href: "http://example.tld/authors/2" } # doc.links << { rel: "copyright", template: "http://example.tld/copyright?id={uri}" } # # doc.to_xml # # @example Parsing a XrdDocument # data = XrdDocument.xml_data(xml_string) # # @see http://docs.oasis-open.org/xri/xrd/v1.0/xrd-1.0.html Extensible Resource Descriptor (XRD) Version 1.0 class XrdDocument # xml namespace url XMLNS = "http://docs.oasis-open.org/ns/xri/xrd-1.0" # +Link+ element attributes LINK_ATTRS = %i(rel type href template) # format string for datetime (+Expires+ element) DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" # The element contains a time value which specifies the instant at # and after which the document has expired and SHOULD NOT be used. # @param [DateTime] value attr_writer :expires # The element contains a URI value which identifies the resource # described by this XRD. # @param [String] value attr_writer :subject # @return [Array] list of alias URIs attr_reader :aliases # @return [Hash mixed>] list of properties. Hash key represents the # +type+ attribute, and the value is the element content attr_reader :properties # @return [Array val>>] list of +Link+ element hashes. Each # hash contains the attributesa and their associated values for the +Link+ # element. attr_reader :links def initialize @aliases = [] @links = [] @properties = {} end # Generates an XML document from the current instance and returns it as string # @return [String] XML document def to_xml builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml| xml.XRD("xmlns" => XMLNS) { xml.Expires(@expires.strftime(DATETIME_FORMAT)) if @expires.instance_of?(DateTime) xml.Subject(@subject) if !@subject.nil? && !@subject.empty? add_aliases_to(xml) add_properties_to(xml) add_links_to(xml) } end builder.to_xml end # Parse the XRD document from the given string and create a hash containing # the extracted data. # # Small bonus: the hash structure that comes out of this method is the same # as the one used to produce a JRD (JSON Resource Descriptor) or parsing it. # # @param [String] xrd_doc XML string # @return [Hash] extracted data # @raise [InvalidDocument] if the XRD is malformed def self.xml_data(xrd_doc) doc = parse_xrd_document(xrd_doc) {}.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? parse_aliases_from_xml_doc(doc, data) parse_properties_from_xml_doc(doc, data) parse_links_from_xml_doc(doc, data) end end private NS = {xrd: XMLNS} def add_aliases_to(xml) @aliases.each do |a| next if !a.instance_of?(String) || a.empty? xml.Alias(a.to_s) end end def add_properties_to(xml) @properties.each do |type, val| xml.Property(val.to_s, type: type) end end def add_links_to(xml) @links.each do |l| attrs = {} LINK_ATTRS.each do |attr| attrs[attr.to_s] = l[attr] if l.key?(attr) end xml.Link(attrs) end end def self.parse_xrd_document(xrd_doc) raise ArgumentError unless xrd_doc.instance_of?(String) doc = Nokogiri::XML::Document.parse(xrd_doc) raise InvalidDocument, "Not an XRD document" if !doc.root || doc.root.name != "XRD" doc end private_class_method :parse_xrd_document def self.parse_aliases_from_xml_doc(doc, data) aliases = [] doc.xpath("xrd:XRD/xrd:Alias", NS).each do |node| aliases << node.content end data[:aliases] = aliases unless aliases.empty? end private_class_method :parse_aliases_from_xml_doc def self.parse_properties_from_xml_doc(doc, data) properties = {} doc.xpath("xrd:XRD/xrd:Property", NS).each do |node| properties[node[:type]] = node.children.empty? ? nil : node.content end data[:properties] = properties unless properties.empty? end private_class_method :parse_properties_from_xml_doc def self.parse_links_from_xml_doc(doc, data) links = [] doc.xpath("xrd:XRD/xrd:Link", NS).each do |node| link = {} LINK_ATTRS.each do |attr| link[attr] = node[attr.to_s] if node.key?(attr.to_s) end links << link end data[:links] = links unless links.empty? end private_class_method :parse_links_from_xml_doc end end end diaspora_federation-0.0.8/lib/diaspora_federation/discovery/exceptions.rb0000644000004100000410000000120312601401764027033 0ustar www-datawww-datamodule DiasporaFederation module Discovery # Raised, if the XML structure is invalid class InvalidDocument < RuntimeError end # Raised, if something is wrong with the webfinger data # # * if the +webfinger_url+ is missing or malformed in {HostMeta.from_base_url} or {HostMeta.from_xml} # * if the parsed XML from {WebFinger.from_xml} is incomplete # * 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 diaspora_federation-0.0.8/lib/diaspora_federation/logging.rb0000644000004100000410000000142412601401764024276 0ustar www-datawww-datamodule DiasporaFederation # logging module for the diaspora federation # # it uses the logging-gem if available module Logging private # get the logger for this class # # use the logging-gem if available, else use a default logger def logger @logger ||= begin # 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) @logger.level = Logger::INFO @logger end end end end diaspora_federation-0.0.8/lib/diaspora_federation/version.rb0000644000004100000410000000010612601401764024331 0ustar www-datawww-datamodule DiasporaFederation # the gem version VERSION = "0.0.8" end diaspora_federation-0.0.8/lib/diaspora_federation/fetcher.rb0000644000004100000410000000217612601401764024275 0ustar www-datawww-datarequire "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 diaspora_federation-0.0.8/lib/diaspora_federation/callbacks.rb0000644000004100000410000000327612601401764024576 0ustar www-datawww-datamodule DiasporaFederation # Callbacks are used to communicate with the application. They are called to # fetch data and after data is received. class Callbacks # Initializes a new Callbacks object with the event-keys that need to be defined. # # @example # Callbacks.new %i( # some_event # another_event # ) # # @param [Hash] events event keys def initialize(events) @events = events @handlers = {} end # defines a callback # # @example # callbacks.on :some_event do |arg1| # # do something # end # # @param [Symbol] event the event key # @param [Proc] callback the callback block # @raise [ArgumentError] if the event key is undefined or has already a handler def on(event, &callback) raise ArgumentError, "Undefined event #{event}" unless @events.include? event raise ArgumentError, "Already defined event #{event}" if @handlers.has_key? event @handlers[event] = callback end # triggers a callback # # @example # callbacks.trigger :some_event, "foo" # # @param [Symbol] event the event key # @return [Object] the return-value of the callback # @raise [ArgumentError] if the event key is undefined def trigger(event, *args) raise ArgumentError, "Undefined event #{event}" unless @events.include? event @handlers[event].call(*args) end # checks if all callbacks are defined # @return [Boolean] def definition_complete? missing_handlers.empty? end # Returns all undefined callbacks # @return [Hash] callback keys def missing_handlers @events - @handlers.keys end end end diaspora_federation-0.0.8/lib/diaspora_federation/properties_dsl.rb0000644000004100000410000000701512601401764025710 0ustar www-datawww-datamodule DiasporaFederation # Provides a simple DSL for specifying {Entity} properties during class # definition. # # @example # 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 # @return [Array] hash of declared entity properties def class_props @class_props ||= [] end # Define a generic (string-type) property # @param [Symbol] name property name # @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 # Define a property that should contain another Entity or an array of # other Entities # @param [Symbol] name property name # @param [Entity, Array] type Entity subclass or # Array with exactly one Entity subclass constant inside # @param [Hash] opts further options # @option opts [Object, #call] :default a default value, making the # property optional def entity(name, type, opts={}) raise InvalidType unless type_valid?(type) define_property name, type, opts end # Return array of missing required property names # @return [Array] missing required property names def missing_props(args) class_prop_names - default_props.keys - args.keys end # Return a new hash of default values, with dynamic values # resolved on each call # @return [Hash] default values def default_values default_props.each_with_object({}) { |(name, prop), hash| hash[name] = prop.respond_to?(:call) ? prop.call : prop } end # Returns all nested Entities # @return [Array] nested properties def nested_class_props @nested_class_props ||= class_props.select {|p| p[:type] != String } end # Returns all property names # @return [Array] property names def class_prop_names @class_prop_names ||= class_props.map {|p| p[:name] } end private def define_property(name, type, opts={}) raise InvalidName unless name_valid?(name) 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 } end # checks if the name is a +Symbol+ or a +String+ # @param [String, Symbol] name the name to check # @return [Boolean] def name_valid?(name) name.instance_of?(Symbol) end # checks if the type extends {Entity} # @param [Class] type the type to check # @return [Boolean] def type_valid?(type) [type].flatten.all? { |type| type.respond_to?(:ancestors) && type.ancestors.include?(Entity) } end def default_props @default_props ||= {} end # Raised, if the name is of an unexpected type class InvalidName < RuntimeError end # Raised, if the type is of an unexpected type class InvalidType < RuntimeError end end end diaspora_federation-0.0.8/lib/diaspora_federation/entities.rb0000644000004100000410000000063312601401764024475 0ustar www-datawww-datamodule 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" diaspora_federation-0.0.8/lib/diaspora_federation/discovery.rb0000644000004100000410000000104012601401764024651 0ustar www-datawww-datamodule 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" diaspora_federation-0.0.8/lib/diaspora_federation/validators/0000755000004100000410000000000012601401764024472 5ustar www-datawww-datadiaspora_federation-0.0.8/lib/diaspora_federation/validators/h_card_validator.rb0000644000004100000410000000161712601401764030311 0ustar www-datawww-datamodule DiasporaFederation module Validators # This validates a {Discovery::HCard} # # @todo activate guid and public key validation after all pod have it in # the hcard. # # @note class HCardValidator < Validation::Validator include Validation # rule :guid, :guid # the name must not contain a semicolon because of mentions # @{ ; } rule :full_name, regular_expression: {regex: /\A[^;]{,70}\z/} rule :first_name, regular_expression: {regex: /\A[^;]{,32}\z/} rule :last_name, regular_expression: {regex: /\A[^;]{,32}\z/} # this urls can be relative rule :photo_large_url, [:not_nil, URI: [:path]] rule :photo_medium_url, [:not_nil, URI: [:path]] rule :photo_small_url, [:not_nil, URI: [:path]] # rule :exported_key, :public_key rule :searchable, :boolean end end end diaspora_federation-0.0.8/lib/diaspora_federation/validators/web_finger_validator.rb0000644000004100000410000000116112601401764031172 0ustar www-datawww-datamodule 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, URI: %i(host path) rule :hcard_url, [:not_nil, URI: %i(host path)] rule :seed_url, %i(not_nil URI) rule :profile_url, [:not_nil, URI: %i(host path)] rule :atom_url, [:not_nil, URI: %i(host path)] rule :salmon_url, URI: %i(host path) end end end diaspora_federation-0.0.8/lib/diaspora_federation/validators/person_validator.rb0000644000004100000410000000055212601401764030374 0ustar www-datawww-datamodule DiasporaFederation module Validators # This validates a {Entities::Person} class PersonValidator < Validation::Validator include Validation rule :guid, :guid rule :diaspora_id, %i(not_empty diaspora_id) rule :url, %i(not_nil URI) rule :profile, :not_nil rule :exported_key, :public_key end end end diaspora_federation-0.0.8/lib/diaspora_federation/validators/rules/0000755000004100000410000000000012601401764025624 5ustar www-datawww-datadiaspora_federation-0.0.8/lib/diaspora_federation/validators/rules/not_nil.rb0000644000004100000410000000065712601401764027623 0ustar www-datawww-datamodule 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 diaspora_federation-0.0.8/lib/diaspora_federation/validators/rules/tag_count.rb0000644000004100000410000000172112601401764030135 0ustar www-datawww-datamodule 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 # create a new rule for a maximum tag count validation # @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 diaspora_federation-0.0.8/lib/diaspora_federation/validators/rules/birthday.rb0000644000004100000410000000163612601401764027765 0ustar www-datawww-datarequire "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 diaspora_federation-0.0.8/lib/diaspora_federation/validators/rules/diaspora_id.rb0000644000004100000410000000277112601401764030436 0ustar www-datawww-datamodule 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) value.nil? || !DIASPORA_ID.match(value).nil? end # This rule has no params # @return [Hash] params def params {} end end end end diaspora_federation-0.0.8/lib/diaspora_federation/validators/rules/boolean.rb0000644000004100000410000000155712601401764027600 0ustar www-datawww-datamodule 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 diaspora_federation-0.0.8/lib/diaspora_federation/validators/rules/public_key.rb0000644000004100000410000000166212601401764030304 0ustar www-datawww-datamodule 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 diaspora_federation-0.0.8/lib/diaspora_federation/validators/rules/guid.rb0000644000004100000410000000120412601401764027076 0ustar www-datawww-datamodule 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 diaspora_federation-0.0.8/lib/diaspora_federation/validators/profile_validator.rb0000644000004100000410000000160112601401764030522 0ustar www-datawww-datamodule 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 # @{ ; } 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, URI: [:path] rule :image_url_medium, URI: [:path] rule :image_url_small, URI: [:path] rule :birthday, :birthday rule :gender, length: {maximum: 255} rule :bio, length: {maximum: 65_535} rule :location, length: {maximum: 255} rule :searchable, :boolean rule :nsfw, :boolean rule :tag_string, tag_count: {maximum: 5} end end end diaspora_federation-0.0.8/lib/diaspora_federation/validators.rb0000644000004100000410000000302212601401764025014 0ustar www-datawww-datarequire "validation" require "validation/rule/regular_expression" require "validation/rule/length" 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/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 "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" diaspora_federation-0.0.8/lib/tasks/0000755000004100000410000000000012601401764017445 5ustar www-datawww-datadiaspora_federation-0.0.8/lib/tasks/build.rake0000644000004100000410000000057112601401764021413 0ustar www-datawww-datadesc "Build gem into the pkg directory" task build: :test do FileUtils.rm_rf("pkg") Dir["*.gemspec"].each do |gemspec| system "gem build #{gemspec}" end FileUtils.mkdir_p("pkg") FileUtils.mv(Dir["*.gem"], "pkg") end desc "Tags version, pushes to remote, and pushes gem" task release: :build do sh "ls pkg/diaspora_federation-*-*.gem | xargs -n 1 gem push" end diaspora_federation-0.0.8/lib/tasks/tests.rake0000644000004100000410000000076212601401764021460 0ustar www-datawww-dataif defined?(RSpec) namespace :spec do task prepare_db: %w(db:create db:test:load) task :prepare_fixtures do ENV["NO_COVERAGE"] = "true" Rake::Task["spec:generate_fixtures"].invoke ENV["NO_COVERAGE"] = "false" end desc "Prepare for rspec" task prepare: %w(prepare_db prepare_fixtures) desc "Run all specs that generate fixtures for rspec" RSpec::Core::RakeTask.new(:generate_fixtures) do |t| t.rspec_opts = ["--tag fixture"] end end end diaspora_federation-0.0.8/lib/tasks/diaspora_federation_tasks.rake0000644000004100000410000000014112601401764025514 0ustar www-datawww-data# desc "Explaining what the task does" # task :diaspora_federation do # # Task goes here # end diaspora_federation-0.0.8/lib/diaspora_federation.rb0000644000004100000410000000551512601401764022655 0ustar www-datawww-datarequire "diaspora_federation/logging" require "diaspora_federation/callbacks" require "diaspora_federation/properties_dsl" require "diaspora_federation/entity" require "diaspora_federation/validators" require "diaspora_federation/fetcher" require "diaspora_federation/entities" require "diaspora_federation/discovery" # diaspora* federation library module DiasporaFederation extend Logging @callbacks = Callbacks.new %i( fetch_person_for_webfinger fetch_person_for_hcard save_person_after_webfinger ) class << self # {Callbacks} instance with defined callbacks # @see Callbacks#on # @see Callbacks#trigger # attr_reader :callbacks # the pod url # # @example with uri # config.server_uri = URI("http://localhost:3000/") # @example with configured pod_uri # 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 # DiasporaFederation.configure do |config| # config.server_uri = URI("http://localhost:3000/") # # config.define_callbacks do # # callback configuration # end # end def configure yield self end # define the callbacks # # @example # config.define_callbacks do # on :some_event do |arg1| # # do something # end # end # # @param [Proc] block the callbacks to define def define_callbacks(&block) @callbacks.instance_eval(&block) end # validates if the engine is configured correctly # # called from after_initialize # @raise [ConfigurationError] if the configuration is incomplete or invalid def validate_config configuration_error "server_uri: Missing or invalid" unless @server_uri.respond_to? :host unless defined?(::Rails) && !::Rails.env.production? 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 end unless @callbacks.definition_complete? configuration_error "Missing handlers for #{@callbacks.missing_handlers.join(', ')}" end logger.info "successfully configured the federation library" end private def configuration_error(message) logger.fatal("diaspora federation configuration error: #{message}") raise ConfigurationError, message end end # raised, if the engine is not configured correctly class ConfigurationError < RuntimeError end end diaspora_federation-0.0.8/metadata.yml0000644000004100000410000001077112601401764020063 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: diaspora_federation version: !ruby/object:Gem::Version version: 0.0.8 platform: ruby authors: - Benjamin Neff autorequire: bindir: bin cert_chain: [] date: 2015-09-19 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: nokogiri requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.6' - - ">=" - !ruby/object:Gem::Version version: 1.6.6 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.6' - - ">=" - !ruby/object:Gem::Version version: 1.6.6 - !ruby/object:Gem::Dependency name: faraday requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 0.9.0 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 0.9.0 - !ruby/object:Gem::Dependency name: faraday_middleware requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 0.10.0 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 0.10.0 - !ruby/object:Gem::Dependency name: typhoeus requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.7' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.7' - !ruby/object:Gem::Dependency name: valid requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.0' description: This gem provides the functionality for de-/serialization and de-/encryption of Entities in the protocols used for communication among the various installations of Diaspora* email: - benjamin@coding4.coffee executables: [] extensions: [] extra_rdoc_files: [] files: - LICENSE - README.md - lib/diaspora_federation.rb - lib/diaspora_federation/callbacks.rb - lib/diaspora_federation/discovery.rb - lib/diaspora_federation/discovery/discovery.rb - lib/diaspora_federation/discovery/exceptions.rb - lib/diaspora_federation/discovery/h_card.rb - lib/diaspora_federation/discovery/host_meta.rb - lib/diaspora_federation/discovery/web_finger.rb - lib/diaspora_federation/discovery/xrd_document.rb - lib/diaspora_federation/entities.rb - lib/diaspora_federation/entities/person.rb - lib/diaspora_federation/entities/profile.rb - lib/diaspora_federation/entity.rb - lib/diaspora_federation/fetcher.rb - lib/diaspora_federation/logging.rb - lib/diaspora_federation/properties_dsl.rb - lib/diaspora_federation/validators.rb - lib/diaspora_federation/validators/h_card_validator.rb - lib/diaspora_federation/validators/person_validator.rb - lib/diaspora_federation/validators/profile_validator.rb - lib/diaspora_federation/validators/rules/birthday.rb - lib/diaspora_federation/validators/rules/boolean.rb - lib/diaspora_federation/validators/rules/diaspora_id.rb - lib/diaspora_federation/validators/rules/guid.rb - lib/diaspora_federation/validators/rules/not_nil.rb - lib/diaspora_federation/validators/rules/public_key.rb - lib/diaspora_federation/validators/rules/tag_count.rb - lib/diaspora_federation/validators/web_finger_validator.rb - lib/diaspora_federation/version.rb - lib/tasks/build.rake - lib/tasks/diaspora_federation_tasks.rake - lib/tasks/tests.rake homepage: https://github.com/SuperTux88/diaspora_federation licenses: - AGPL 3.0 - http://www.gnu.org/licenses/agpl-3.0.html metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '2.0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.4.6 signing_key: specification_version: 4 summary: diaspora* federation library test_files: [] has_rdoc: diaspora_federation-0.0.8/LICENSE0000644000004100000410000000146612601401764016566 0ustar www-datawww-dataDiaspora Federation Rails Engine Copyright (C) 2015 Benjamin Neff This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Some parts are based on an older federation gem from Florian Staudacher: https://github.com/Raven24/diaspora-federation diaspora_federation-0.0.8/README.md0000644000004100000410000000537012601401764017036 0ustar www-datawww-data# diaspora* federation library **A library that provides functionalities needed for the diaspora* federation protocol** [![Build Status](https://travis-ci.org/SuperTux88/diaspora_federation.svg?branch=master)](https://travis-ci.org/SuperTux88/diaspora_federation) [![Code Climate](https://codeclimate.com/github/SuperTux88/diaspora_federation/badges/gpa.svg)](https://codeclimate.com/github/SuperTux88/diaspora_federation) [![Test Coverage](https://codeclimate.com/github/SuperTux88/diaspora_federation/badges/coverage.svg)](https://codeclimate.com/github/SuperTux88/diaspora_federation/coverage) [![Dependency Status](https://gemnasium.com/SuperTux88/diaspora_federation.svg)](https://gemnasium.com/SuperTux88/diaspora_federation) [![Inline docs](https://inch-ci.org/github/SuperTux88/diaspora_federation.svg?branch=master)](https://inch-ci.org/github/SuperTux88/diaspora_federation) [![Gem Version](https://badge.fury.io/rb/diaspora_federation.svg)](https://badge.fury.io/rb/diaspora_federation) [Documentation](http://www.rubydoc.info/gems/diaspora_federation/) | [Bugtracker](https://github.com/SuperTux88/diaspora_federation/issues) ## Library The ```diaspora_federation``` gem provides the functionality for de-/serialization and de-/encryption of Entities in the protocols used for communication among the various installations of Diaspora* ## Rails Engine The ```diaspora_federation-rails``` gem is a rails engine that adds the diaspora* federation protocol to a rails app. ### Usage Add the gem to your ```Gemfile```: ```ruby gem "diaspora_federation-rails" ``` Mount the routes in your ```config/routes.rb```: ```ruby mount DiasporaFederation::Engine => "/" ``` Configure the engine in ```config/initializers/diaspora_federation.rb```: ```ruby DiasporaFederation.configure do |config| # the pod url config.server_uri = AppConfig.pod_uri config.define_callbacks do on :fetch_person_for_webfinger do |diaspora_id| person = Person.find_local_by_diaspora_id(diaspora_id) if person DiasporaFederation::Discovery::WebFinger.new( # ... ) end end on :fetch_person_for_hcard do |guid| # ... end end end ``` ## Development **!!! This gem is currently under heavy development, so every release can contain breaking changes !!!** If you want to help, please contact me, help is welcome. After the first stable release, this repo will be moved to the [diaspora organization](https://github.com/diaspora/). ## Diaspora A privacy-aware, distributed, open source social network Links: [Project site](https://diasporafoundation.org) | [Wiki](https://wiki.diasporafoundation.org) ## License This gem is published under the terms of the "GNU Affero General Public License". See the LICENSE file for the exact wording.