social_stream-ostatus 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. data/app/assets/images/logos/contact/remote_subject.png +0 -0
  2. data/app/assets/images/logos/original/remote_subject.png +0 -0
  3. data/app/assets/images/logos/profile/remote_subject.png +0 -0
  4. data/app/controllers/pshb_controller.rb +14 -22
  5. data/app/controllers/remote_subjects_controller.rb +19 -0
  6. data/app/controllers/salmon_controller.rb +15 -0
  7. data/app/controllers/webfinger_controller.rb +12 -3
  8. data/app/decorators/social_stream/base/activity_decorator.rb +3 -0
  9. data/app/decorators/social_stream/base/actor_decorator.rb +3 -0
  10. data/app/decorators/social_stream/base/audience_decorator.rb +3 -0
  11. data/app/decorators/social_stream/base/relation/custom_decorator.rb +3 -0
  12. data/app/decorators/social_stream/base/tie_decorator.rb +3 -0
  13. data/app/models/actor_key.rb +30 -0
  14. data/app/models/remote_subject.rb +111 -15
  15. data/app/views/remote_subjects/_show.html.erb +6 -0
  16. data/app/views/remote_subjects/show.html.erb +5 -0
  17. data/config/routes.rb +7 -4
  18. data/db/migrate/20120905145030_create_social_stream_ostatus.rb +2 -1
  19. data/db/migrate/20120918194708_create_actor_keys.rb +18 -0
  20. data/lib/generators/social_stream/ostatus/install_generator.rb +0 -6
  21. data/lib/generators/social_stream/ostatus/templates/initializer.rb +16 -2
  22. data/lib/social_stream/ostatus/activity_streams.rb +79 -0
  23. data/lib/social_stream/ostatus/controllers/debug_requests.rb +24 -0
  24. data/lib/social_stream/ostatus/engine.rb +6 -6
  25. data/lib/social_stream/ostatus/models/activity.rb +41 -0
  26. data/lib/social_stream/ostatus/models/actor.rb +60 -14
  27. data/lib/social_stream/ostatus/models/audience.rb +2 -2
  28. data/lib/social_stream/ostatus/models/object.rb +28 -0
  29. data/lib/social_stream/ostatus/models/relation/custom.rb +22 -0
  30. data/lib/social_stream/ostatus/models/tie.rb +24 -0
  31. data/lib/social_stream/ostatus/version.rb +1 -1
  32. data/lib/social_stream-ostatus.rb +31 -4
  33. data/social_stream-ostatus.gemspec +2 -2
  34. data/spec/controllers/host_meta_controller_spec.rb +10 -0
  35. data/spec/controllers/remote_subjects_controller_spec.rb +37 -0
  36. data/spec/controllers/webfinger_controller_spec.rb +13 -0
  37. data/spec/factories/remote_subject.rb +39 -0
  38. data/spec/models/actor_key_spec.rb +9 -0
  39. data/spec/models/post_spec.rb +25 -0
  40. data/spec/models/remote_subject_spec.rb +24 -0
  41. data/spec/social_stream_ostatus_activity_streams.spec.rb +31 -0
  42. metadata +34 -8
  43. data/app/controllers/remoteusers_controller.rb +0 -30
  44. data/app/views/remoteusers/index.html.erb +0 -8
@@ -1,34 +1,26 @@
1
1
  class PshbController < ApplicationController
2
-
3
- def callback
4
- #sync subscription verification
5
- if params['hub.mode']=='subscribe'
2
+ include SocialStream::Ostatus::Controllers::DebugRequests
3
+
4
+ skip_before_filter :verify_authenticity_token
5
+
6
+ def index
7
+ case params['hub.mode']
8
+ #TODO check PuSH specification about subscribe or async
9
+ when 'subscribe', 'async'
6
10
  render :text => params['hub.challenge'], :status => 200
7
- # TO-DO: confirm that params['hub.topic'] is a real
11
+ # TODO: confirm that params['hub.topic'] is a real
8
12
  # requested subscription by someone in this node
9
13
  return
10
- end
11
-
12
- #sync unsubscription verification
13
- if params['hub.mode']=='unsubscribe'
14
+ when 'unsubscribe'
14
15
  render :text => params['hub.challenge'], :status => 200
15
- # TO-DO: confirm that params['hub.topic'] is a real
16
+ # TODO: confirm that params['hub.topic'] is a real
16
17
  # requested unsubscription by someone in this node
17
18
  # and delete permissions/remote actor if necessary
18
19
  return
19
20
  end
20
21
 
21
- #If we got here we are receiving an XML Activity Feed
22
- doc = Nokogiri::XML(request.body.read)
23
- origin = doc.xpath("//xmlns:link[@rel='self']").first['href'].split('/')
24
- webfinger_id = origin[5]+"@"+origin[2]
25
-
26
- activity_texts = doc.xpath("//xmlns:content")
27
- activity_texts.each do |activity_text|
28
- r_user = RemoteSubject.find_by_webfinger_id(webfinger_id)
29
- if r_user != nil
30
- Post.create!(:text => activity_text.content, :_activity_tie_id => r_user.public_tie)
31
- end
32
- end
22
+ SocialStream::ActivityStreams.from_pshb_callback(request.body.read)
23
+
24
+ render text: "Success!"
33
25
  end
34
26
  end
@@ -0,0 +1,19 @@
1
+ class RemoteSubjectsController < ApplicationController
2
+ def index
3
+ raise ActiveRecord::NotFound if params[:q].blank?
4
+
5
+ @remote_subject =
6
+ RemoteSubject.find_or_create_by_webfinger_uri!(params[:q])
7
+
8
+ redirect_to @remote_subject
9
+ end
10
+
11
+ def show
12
+ @remote_subject =
13
+ RemoteSubject.find_by_slug!(params[:id])
14
+
15
+ if params[:refresh]
16
+ @remote_subject.refresh_webfinger!
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ class SalmonController < ApplicationController
2
+ include SocialStream::Ostatus::Controllers::DebugRequests
3
+
4
+ skip_before_filter :verify_authenticity_token
5
+
6
+ def index
7
+ actor = Actor.find_by_slug! params[:slug]
8
+
9
+ SocialStream::ActivityStreams.from_salmon_callback request.body.read, actor
10
+
11
+ # TODO handle errors
12
+
13
+ render text: "Success!"
14
+ end
15
+ end
@@ -5,9 +5,18 @@ class WebfingerController < ActionController::Metal
5
5
  def index
6
6
  actor = Actor.find_by_webfinger!(params[:q])
7
7
 
8
- finger = Proudhon::Finger.new :links => {
9
- :profile => polymorphic_url([actor.subject, :profile])
10
- }
8
+ finger = Proudhon::Finger.new(
9
+ :subject => actor.webfinger_uri,
10
+ :alias => [polymorphic_url(actor.subject)],
11
+ :links => {
12
+ avatar: root_url + actor.logo.url(:original),
13
+ profile: polymorphic_url([actor.subject, :profile]),
14
+ updates_from: polymorphic_url([actor.subject, :activities], :format => :atom),
15
+ salmon: salmon_url(actor.slug),
16
+ replies: salmon_url(actor.slug),
17
+ mention: salmon_url(actor.slug),
18
+ magic_key: actor.magic_public_key
19
+ })
11
20
 
12
21
  self.response_body = finger.to_xml
13
22
  self.content_type = Mime::XML
@@ -0,0 +1,3 @@
1
+ Activity.class_eval do
2
+ include SocialStream::Ostatus::Models::Activity
3
+ end
@@ -0,0 +1,3 @@
1
+ Actor.class_eval do
2
+ include SocialStream::Ostatus::Models::Actor
3
+ end
@@ -0,0 +1,3 @@
1
+ Audience.class_eval do
2
+ include SocialStream::Ostatus::Models::Audience
3
+ end
@@ -0,0 +1,3 @@
1
+ Relation::Custom.class_eval do
2
+ include SocialStream::Ostatus::Models::Relation::Custom
3
+ end
@@ -0,0 +1,3 @@
1
+ Tie.class_eval do
2
+ include SocialStream::Ostatus::Models::Tie
3
+ end
@@ -0,0 +1,30 @@
1
+ require 'openssl'
2
+
3
+ # Store OStatus private and public key
4
+ class ActorKey < ActiveRecord::Base
5
+ KEY_SIZE = 1024
6
+
7
+ belongs_to :actor
8
+
9
+ validates_presence_of :key_der
10
+
11
+ before_validation :generate_key, on: :create
12
+
13
+ def key
14
+ @key ||=
15
+ OpenSSL::PKey::RSA.new(key_der)
16
+ end
17
+
18
+ def key= new_key
19
+ @key = new_key
20
+ self.key_der = new_key.to_der
21
+ end
22
+
23
+ private
24
+
25
+ def generate_key
26
+ return if key_der.present?
27
+
28
+ self.key = OpenSSL::PKey::RSA.generate(KEY_SIZE)
29
+ end
30
+ end
@@ -1,27 +1,53 @@
1
1
  class RemoteSubject < ActiveRecord::Base
2
- attr_accessible :name, :webfinger_id, :origin_node_url
2
+ include SocialStream::Models::Subject
3
+ # Create absolute routes
4
+ include Rails.application.routes.url_helpers
5
+
6
+ attr_reader :url_helper
7
+ attr_accessible :webfinger_id
8
+
9
+ # Save webfinger_info hash into the database
10
+ serialize :webfinger_info
11
+
12
+ validates_uniqueness_of :webfinger_id
13
+
14
+ before_validation :fill_information,
15
+ :on => :create
16
+
17
+ after_create :subscribe_to_public_feed
18
+ after_destroy :unsubscribe_to_public_feed
19
+
20
+ scope :webfinger_alias, lambda { |uri|
21
+ where('webfinger_info LIKE ?', "%aliases%#{ uri }%")
22
+ }
3
23
 
4
24
  #validates_format_of :webfinger_slug, :with => Devise.email_regexp, :allow_blank => true
5
25
 
6
26
  class << self
7
- def find_or_create_using_webfinger_id(id)
8
- subject = RemoteSubject.find_by_webfinger_id(id)
27
+ def find_or_create_by_webfinger_uri!(uri)
28
+ if uri =~ /^https?:\/\//
29
+ records = webfinger_alias(uri)
9
30
 
10
- return subject if subject.present?
31
+ # SQL scope is not reliable
32
+ if records.present?
33
+ return records.find{ |r| r.webfinger_aliases.include?(uri) }
34
+ end
11
35
 
12
- RemoteSubject.create! :name => id,
13
- :webfinger_id => id
36
+ # TODO: create by http uri?
37
+
38
+ raise ::ActiveRecord::RecordNotFound
39
+ end
40
+
41
+ id = uri.dup
42
+
43
+ if id =~ /^acct:/
44
+ id.gsub!('acct:', '')
45
+ end
46
+
47
+ find_or_create_by_webfinger_id!(id)
14
48
  end
15
49
  end
16
50
 
17
- # Public feed url for this RemoteSubject
18
- #
19
- # TODO: get from webfinger?
20
- # It does not work for every remote user!
21
- def public_feed_url
22
- "http://#{ webfinger_url }/api/user/#{ name }/public.atom"
23
- end
24
-
25
51
  # Return the slug in the webfinger_id
26
52
  def webfinger_slug
27
53
  splitted_webfinger_id.first
@@ -32,12 +58,82 @@ class RemoteSubject < ActiveRecord::Base
32
58
  splitted_webfinger_id.last
33
59
  end
34
60
 
35
- protected
61
+ # URL of the activity feed from this {RemoteSubject}
62
+ def public_feed_url
63
+ webfinger_info[:updates_from]
64
+ end
65
+
66
+ # URL of the Salmon endpoint for this {RemoteSubject}
67
+ def salmon_url
68
+ webfinger_info[:salmon]
69
+ end
70
+
71
+ # Webfinger Alias
72
+ def webfinger_aliases
73
+ webfinger_info[:aliases]
74
+ end
75
+
76
+ # Fetch the webfinger again
77
+ def refresh_webfinger!
78
+ fill_webfinger_info
79
+
80
+ save!
81
+ end
82
+
83
+ private
36
84
 
37
85
  def splitted_webfinger_id
38
86
  @splitted_webfinger_id ||=
39
87
  webfinger_id.split('@')
40
88
  end
41
89
 
90
+ def fill_information
91
+ fill_webfinger_info
92
+
93
+ self.name = webfinger_id
94
+ end
95
+
96
+ def fill_webfinger_info
97
+ self.webfinger_info = build_webfinger_info
98
+ self.rsa_key = finger.magic_key
99
+ end
100
+
101
+ def build_webfinger_info
102
+ {
103
+ updates_from: finger.links[:updates_from],
104
+ salmon: finger.links[:salmon],
105
+ aliases: finger.alias
106
+ }
107
+ end
108
+
109
+ def finger
110
+ @finger ||=
111
+ fetch_finger
112
+ end
113
+
114
+ def fetch_finger
115
+ finger =
116
+ Proudhon::Finger.fetch webfinger_id
42
117
 
118
+ # FIXME custom error
119
+ raise ::ActiveRecord::RecordNotFound if finger.blank?
120
+
121
+ finger
122
+ end
123
+
124
+ def subscribe_to_public_feed
125
+ return if public_feed_url.blank?
126
+
127
+ atom = Proudhon::Atom.from_uri(public_feed_url)
128
+
129
+ atom.subscribe(pshb_url(:host => SocialStream::Ostatus.pshb_host))
130
+ end
131
+
132
+ def unsubscribe_to_public_feed
133
+ return if public_feed_url.blank?
134
+
135
+ atom = Proudhon::Atom.from_uri(public_feed_url)
136
+
137
+ atom.unsubscribe(pshb_url(:host => SocialStream::Ostatus.pshb_host))
138
+ end
43
139
  end
@@ -0,0 +1,6 @@
1
+ <% toolbar :profile, subject: @remote_subject %>
2
+
3
+ <%= render partial: "activities/index",
4
+ locals: { activities: @remote_subject.wall(:profile, for: current_subject).
5
+ page(params[:page]),
6
+ owner: @remote_subject } %>
@@ -0,0 +1,5 @@
1
+ <% content_for :title do %>
2
+ <%= @remote_subject.name+" | "+t('site.name') %>
3
+ <% end %>
4
+
5
+ <%= render :partial => 'show' %>
data/config/routes.rb CHANGED
@@ -1,10 +1,13 @@
1
1
  Rails.application.routes.draw do
2
- # Webfinger
2
+ # Host Meta
3
3
  match '/.well-known/host-meta', :to => HostMetaController.action(:index)
4
4
 
5
- # Find subjects by slug
5
+ # Webfinger
6
6
  match '/webfinger' => 'webfinger#index', :as => 'webfinger'
7
7
 
8
- match 'pshb/callback' => 'pshb#callback', :as => :pshb_callback
9
- match 'remoteuser/' => 'remoteusers#index', :as => :add_remote_user
8
+ # PushSubHubBub callback
9
+ match 'pshb' => 'pshb#index', as: :pshb
10
+
11
+ # Salmon callback
12
+ match 'salmon/:slug' => 'salmon#index', as: :salmon
10
13
  end
@@ -3,7 +3,8 @@ class CreateSocialStreamOstatus < ActiveRecord::Migration
3
3
  def self.up
4
4
  create_table :remote_subjects, :force => true do |t|
5
5
  t.integer :actor_id
6
- t.string :webfinger_id
6
+ t.string :webfinger_id
7
+ t.text :webfinger_info
7
8
  t.timestamps
8
9
  end
9
10
 
@@ -0,0 +1,18 @@
1
+ class CreateActorKeys < ActiveRecord::Migration
2
+ def up
3
+ create_table :actor_keys do |t|
4
+ t.integer :actor_id
5
+ t.binary :key_der
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index "actor_keys", "actor_id"
11
+ add_foreign_key "actor_keys", "actors", :name => "actor_keys_on_actor_id"
12
+ end
13
+
14
+ def down
15
+ remove_foreign_key "actor_keys", :name => "actor_keys_on_actor_id"
16
+ drop_table :actor_keys
17
+ end
18
+ end
@@ -13,10 +13,4 @@ class SocialStream::Ostatus::InstallGenerator < Rails::Generators::Base
13
13
  def config_initializer
14
14
  copy_file 'initializer.rb', 'config/initializers/social_stream-ostatus.rb'
15
15
  end
16
-
17
- def inject_remote_user_relation
18
- append_file 'config/relations.yml',
19
- "\nremote_subject:\n friend:\n name: friend\n permissions:\n - [ follow ]\n sphere: personal\n"+
20
- " public:\n name: public\n permissions:\n - [ read, tie, star_tie ]\n sphere: personal"
21
- end
22
16
  end
@@ -1,4 +1,18 @@
1
1
  SocialStream::Ostatus.setup do |config|
2
- config.hub = 'http://localhost:4567/'
3
- config.node_base_url = 'http://localhost:3000'
2
+ # Default to the PuSH reference Hub server
3
+ #
4
+ # config.hub = 'http://pubsubhubbub.appspot.com'
5
+
6
+ # The host where the hub should take the activity feed from
7
+ #
8
+ # Local subjects will publish their public activities there
9
+ config.activity_feed_host = 'localhost:3000'
10
+
11
+ # The host where the PuSH should send the callbacks to
12
+ #
13
+ # Remote subjects get their local activities updates with the PuSH callback
14
+ config.pshb_host = 'localhost:3000'
15
+
16
+ # Debug OStatus requests
17
+ # config.debug_requests = true
4
18
  end
@@ -0,0 +1,79 @@
1
+ module SocialStream
2
+ module Ostatus
3
+ module ActivityStreams
4
+ # Parses the body from a {PshbController#index} and dispatches
5
+ # entries for parsing to {#record_from_entry!}
6
+ def from_pshb_callback(body)
7
+ atom = Proudhon::Atom.parse body
8
+
9
+ atom.entries.each do |entry|
10
+ # FIXME: get author from feed
11
+ # https://github.com/shf/proudhon/issues/8
12
+ entry.author.uri ||= feed.author.uri
13
+
14
+ activity_from_entry! entry
15
+ end
16
+ end
17
+
18
+ # Parses an activity form a PuSH or Salmon notification
19
+ # Decides what action should be taken from an ActivityStreams entry
20
+ def activity_from_entry! entry, receiver = nil
21
+ # FIXME: should not use to_sym
22
+ # https://github.com/shf/proudhon/issues/7
23
+ case entry.verb.to_sym
24
+ when :follow
25
+ Tie.from_entry! entry, receiver
26
+ else
27
+ # :post is the default verb
28
+ r = record_from_entry! entry, receiver
29
+ r.post_activity
30
+ end
31
+ end
32
+
33
+ # Redirects parsing to the suitable SocialStream's model
34
+ def record_from_entry! entry, receiver
35
+ model!(entry.objtype).from_entry! entry, receiver
36
+ end
37
+
38
+ # Finds or creates a {RemoteSubject} from an ActivityStreams entry
39
+ #
40
+ def actor_from_entry! entry
41
+ webfinger_id = entry.author.uri
42
+
43
+ if webfinger_id.blank?
44
+ raise "Entry author without uri: #{ entry.to_xml }"
45
+ end
46
+
47
+ RemoteSubject.find_or_create_by_webfinger_uri! webfinger_id
48
+ end
49
+
50
+ # Parses the body from a {Salmon#index} and receiving actor
51
+ def from_salmon_callback(body, receiver)
52
+ salmon = Proudhon::Salmon.new body
53
+
54
+ validate_salmon salmon
55
+
56
+ activity_from_entry! salmon.to_entry, receiver
57
+ end
58
+
59
+ def validate_salmon salmon
60
+ remote_subject = RemoteSubject.find_or_create_by_webfinger_uri!(salmon.to_entry.author.uri)
61
+ key = remote_subject.rsa_key
62
+
63
+ unless salmon.verify(key)
64
+ raise "Invalid salmon: #{ salmon }"
65
+ end
66
+ end
67
+
68
+ # Translate SocialStream activity verb to Proudhon verb
69
+ def verb orig
70
+ case orig
71
+ when 'make-friend'
72
+ :follow
73
+ else
74
+ orig.to_sym
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,24 @@
1
+ module SocialStream
2
+ module Ostatus
3
+ module Controllers
4
+ module DebugRequests
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ before_filter :debug_request
9
+ end
10
+
11
+ private
12
+
13
+ def debug_request
14
+ return unless SocialStream::Ostatus.debug_requests
15
+
16
+ logger.info request.body.read
17
+
18
+ # Set StringIO to initial state for the action to get the content
19
+ request.body.rewind
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,15 +1,15 @@
1
1
  module SocialStream
2
2
  module Ostatus
3
3
  class Engine < Rails::Engine
4
- initializer 'social_stream-ostatus.actor' do
5
- ActiveSupport.on_load(:actor) do
6
- include SocialStream::Ostatus::Models::Actor
4
+ initializer 'social_stream-ostatus.activity_streams' do
5
+ SocialStream::ActivityStreams.class_eval do
6
+ extend SocialStream::Ostatus::ActivityStreams
7
7
  end
8
8
  end
9
9
 
10
- initializer 'social_stream-ostatus.audience' do
11
- ActiveSupport.on_load(:audience) do
12
- include SocialStream::Ostatus::Models::Audience
10
+ initializer 'social_stream-ostatus.models.object' do
11
+ SocialStream::Models::Object::ClassMethods.module_eval do
12
+ include SocialStream::Ostatus::Models::Object::ClassMethods
13
13
  end
14
14
  end
15
15
 
@@ -0,0 +1,41 @@
1
+ module SocialStream
2
+ module Ostatus
3
+ module Models
4
+ module Activity
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ after_commit :send_salmon
9
+ end
10
+
11
+ private
12
+
13
+ # Send Salmon notification to remote subject
14
+ def send_salmon
15
+ return if sender.subject_type == "RemoteSubject" ||
16
+ receiver.subject_type != "RemoteSubject"
17
+
18
+ entry =
19
+ Proudhon::Entry.new id: "tag:#{ SocialStream::Ostatus.activity_feed_host },2005:activity-#{ id }",
20
+ title: stream_title,
21
+ content: stream_content,
22
+ verb: SocialStream::ActivityStreams.verb(verb),
23
+ author: Proudhon::Author.new(name: sender.name,
24
+ uri: sender.webfinger_uri)
25
+ salmon = entry.to_salmon
26
+
27
+ if SocialStream::Ostatus.debug_requests
28
+ logger.info entry.to_xml
29
+ end
30
+
31
+ # FIXME: Rails 4 queues
32
+ Thread.new do
33
+ salmon.deliver receiver_subject.salmon_url, sender.rsa_key
34
+
35
+ ActiveRecord::Base.connection.close
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -6,9 +6,15 @@ module SocialStream
6
6
  module Models
7
7
  module Actor
8
8
  extend ActiveSupport::Concern
9
+
10
+ include Rails.application.routes.url_helpers
9
11
 
10
12
  included do
11
- after_create :init_feeds_to_hub
13
+ has_one :actor_key, dependent: :destroy,
14
+ validate: true,
15
+ autosave: true
16
+
17
+ after_commit :publish_feed
12
18
  end
13
19
 
14
20
  module ClassMethods
@@ -20,21 +26,61 @@ module SocialStream
20
26
  find_by_slug! $2
21
27
  end
22
28
  end
23
-
24
- def init_feeds_to_hub
25
- publish_or_update_public_feed
26
- #TO-DO: add calls to other public feeds if any
29
+
30
+ # The Webfinger ID for this {Actor}
31
+ def webfinger_id
32
+ "#{ slug }@#{ SocialStream::Ostatus.activity_feed_host }"
27
33
  end
28
-
29
- def publish_or_update_public_feed
30
- t = Thread.new do
31
- hub = SocialStream::Ostatus.hub
32
- topic = SocialStream::Ostatus.node_base_url+'/api/user/'+self.slug+'/public.atom'
34
+
35
+ # The Webfinger URI for this {Actor}
36
+ def webfinger_uri
37
+ "acct:#{ webfinger_id }"
38
+ end
39
+
40
+ # Fetch or create the associated {ActorKey}
41
+ def actor_key!
42
+ actor_key ||
43
+ create_actor_key!
44
+ end
45
+
46
+ # OpenSSL::PKey::RSA key
47
+ #
48
+ # The key is generated if it does not exist
49
+ def rsa_key
50
+ actor_key!.key
51
+ end
52
+
53
+ # Set OpenSSL::PKey::RSA key
54
+ def rsa_key= key
55
+ k = actor_key || build_actor_key
56
+ k.key = key
57
+ end
58
+
59
+ # Public RSA instance of {#rsa_key}
60
+ def rsa_public_key
61
+ rsa_key.public_key
62
+ end
63
+
64
+ # MagicKey string from public key
65
+ def magic_public_key
66
+ Proudhon::MagicKey.to_s rsa_public_key
67
+ end
68
+
69
+ def publish_feed
70
+ return if subject_type == "RemoteSubject"
71
+
72
+ # FIXME: Rails 4 queues
73
+ Thread.new do
74
+ uri = URI.parse(SocialStream::Ostatus.hub)
75
+ topic = polymorphic_url [subject, :activities],
76
+ :format => :atom,
77
+ :host => SocialStream::Ostatus.activity_feed_host
33
78
 
34
- uri = URI.parse(hub)
35
- response = Net::HTTP::post_form(uri,{ 'hub.mode' => 'publish',
36
- 'hub.url' => topic})
37
- #TO-DO: process 4XX look at: response.status
79
+ response = Net::HTTP::post_form uri, { 'hub.mode' => 'publish',
80
+ 'hub.url' => topic }
81
+ #TODO: process 4XX look at: response.status
82
+
83
+ ActiveRecord::Base.connection.close
38
84
  end
39
85
  end
40
86
  end
@@ -9,8 +9,8 @@ module SocialStream
9
9
  end
10
10
 
11
11
  def update_feed_to_hub
12
- if relation.is_a?(Relation::Public)
13
- activity.owner.publish_or_update_public_feed
12
+ if relation.is_a?(::Relation::Public)
13
+ activity.owner.publish_feed
14
14
  end
15
15
  end
16
16
  end
@@ -0,0 +1,28 @@
1
+ module SocialStream
2
+ module Ostatus
3
+ module Models
4
+ module Object
5
+ module ClassMethods
6
+ # Creates an new instance from ActivityStreams entry
7
+ #
8
+ def from_entry! entry, receiver
9
+ create! do |obj|
10
+ obj.author =
11
+ obj.user_author =
12
+ SocialStream::ActivityStreams.actor_from_entry!(entry)
13
+
14
+ obj.owner = receiver || obj.author
15
+
16
+ obj.title = entry.title
17
+ obj.description = entry.summary || entry.content
18
+
19
+ obj.relation_ids = [ ::Relation::Public.instance.id ]
20
+
21
+ yield obj if block_given?
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ module SocialStream
2
+ module Ostatus
3
+ module Models
4
+ module Relation
5
+ module Custom
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ const_get("DEFAULT")['remote_subject'] = {
10
+ 'default' => {
11
+ 'name' => 'default',
12
+ 'permissions' => [
13
+ [ 'read', 'activity' ]
14
+ ]
15
+ }
16
+ }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ module SocialStream
2
+ module Ostatus
3
+ module Models
4
+ module Tie
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ # Create a new {Tie} from OStatus entry
9
+ def from_entry! entry, receiver
10
+ # Sender must be remote
11
+ sender = RemoteSubject.find_or_create_by_webfinger_uri! entry.author.uri
12
+
13
+ contact = sender.contact_to!(receiver)
14
+
15
+ # FIXME: hack
16
+ contact.user_author = sender
17
+
18
+ contact.relation_ids = [::Relation::Public.instance.id]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,5 +1,5 @@
1
1
  module SocialStream
2
2
  module Ostatus
3
- VERSION = "0.0.1".freeze
3
+ VERSION = "0.1.0".freeze
4
4
  end
5
5
  end
@@ -5,21 +5,48 @@ require 'proudhon'
5
5
 
6
6
  module SocialStream
7
7
  module Ostatus
8
+ # PuSH hub
8
9
  mattr_accessor :hub
9
- @@hub = :hub
10
-
11
- mattr_accessor :node_base_url
12
- @@node_base_url = :node_base_url
10
+ # Default to the PubSubHubbub reference Hub server
11
+ @@hub = 'http://pubsubhubbub.appspot.com'
13
12
 
13
+ # The host where the hub should take the activity feed from
14
+ mattr_accessor :activity_feed_host
15
+ @@activity_feed_host = 'localhost:3000'
16
+
17
+ # The host where the PuSH should send the callbacks to
18
+ mattr_accessor :pshb_host
19
+ @@pshb_host = 'localhost:3000'
20
+
21
+ # Debug OStatus request with logger.info
22
+ mattr_accessor :debug_requests
23
+ @@debug_requests = false
24
+
14
25
  class << self
15
26
  def setup
16
27
  yield self
17
28
  end
18
29
  end
19
30
 
31
+ autoload :ActivityStreams, 'social_stream/ostatus/activity_streams'
32
+
20
33
  module Models
34
+ autoload :Activity, 'social_stream/ostatus/models/activity'
21
35
  autoload :Actor, 'social_stream/ostatus/models/actor'
22
36
  autoload :Audience, 'social_stream/ostatus/models/audience'
37
+ autoload :Tie, 'social_stream/ostatus/models/tie'
38
+
39
+ module Object
40
+ autoload :ClassMethods, 'social_stream/ostatus/models/object'
41
+ end
42
+
43
+ module Relation
44
+ autoload :Custom, 'social_stream/ostatus/models/relation/custom'
45
+ end
46
+ end
47
+
48
+ module Controllers
49
+ autoload :DebugRequests, 'social_stream/ostatus/controllers/debug_requests'
23
50
  end
24
51
  end
25
52
  end
@@ -12,8 +12,8 @@ Gem::Specification.new do |s|
12
12
  s.files = `git ls-files`.split("\n")
13
13
 
14
14
  # Gem dependencies
15
- s.add_runtime_dependency('social_stream-base','~> 0.22.0')
16
- s.add_runtime_dependency('proudhon','>= 0.3')
15
+ s.add_runtime_dependency('social_stream-base', '~> 0.23.0')
16
+ s.add_runtime_dependency('proudhon','>= 0.3.5')
17
17
  s.add_runtime_dependency('nokogiri','> 1.4.4')
18
18
 
19
19
  # Development Gem dependencies
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe HostMetaController do
4
+ render_views
5
+
6
+ it "should render host_meta" do
7
+ get :index, :format => :all
8
+ assert_response :success
9
+ end
10
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe RemoteSubjectsController do
4
+ render_views
5
+
6
+ context "with remote subject" do
7
+ before do
8
+ @remote_subject = Factory(:remote_subject)
9
+ end
10
+
11
+ it "should redirect index to show" do
12
+ get :index, q: @remote_subject.webfinger_id
13
+
14
+ response.should redirect_to(@remote_subject)
15
+ end
16
+
17
+ it "should render show" do
18
+ get :show, id: @remote_subject.slug
19
+
20
+ response.should be_success
21
+ end
22
+
23
+
24
+ describe "refreshing show" do
25
+ before do
26
+ RemoteSubject.should_receive(:find_by_slug!).with(@remote_subject.slug) { @remote_subject }
27
+ @remote_subject.should_receive(:refresh_webfinger!)
28
+ end
29
+
30
+ it "should refresh remote_subject" do
31
+ get :show, id: @remote_subject.slug, refresh: true
32
+
33
+ response.should be_success
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe WebfingerController do
4
+ before do
5
+ @user = Factory(:user)
6
+ end
7
+
8
+ it "should render index" do
9
+ get :index, q: @user.webfinger_id
10
+
11
+ response.should be_success
12
+ end
13
+ end
@@ -0,0 +1,39 @@
1
+ class << Proudhon::Finger
2
+ def fetch id
3
+ obj = Object.new
4
+ def obj.links
5
+ { updates_from: 'feed' }
6
+ end
7
+
8
+ def obj.magic_key
9
+ OpenSSL::PKey::RSA.generate 256
10
+ end
11
+
12
+ obj
13
+ end
14
+ end
15
+
16
+ class << Proudhon::Atom
17
+ def from_uri uri
18
+ obj = Object.new
19
+
20
+ def obj.subscribe(callback)
21
+ true
22
+ end
23
+
24
+ obj
25
+ end
26
+ end
27
+
28
+ RemoteSubject.class_eval do
29
+ def build_webfinger_info
30
+ {
31
+ aliases: [ "http://example.com/#{ webfinger_id.split('@').first }" ]
32
+ }
33
+
34
+ end
35
+ end
36
+
37
+ Factory.define :remote_subject do |s|
38
+ s.sequence(:webfinger_id) { |n| "remote_subject-#{ n }@example.com" }
39
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActorKey do
4
+ it "should generate openssl key" do
5
+ actor_key = ActorKey.create! actor_id: 1
6
+
7
+ actor_key.key.class.should == OpenSSL::PKey::RSA
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe Post do
4
+ describe "from_entry!" do
5
+ before do
6
+ @remote_subject = Factory(:remote_subject)
7
+ @entry = double("Proudhon::Entry")
8
+
9
+ SocialStream::ActivityStreams.should_receive(:actor_from_entry!) { @remote_subject }
10
+
11
+ @entry.should_receive(:title) { "testing" }
12
+ @entry.should_receive(:summary) { "testing" }
13
+
14
+ end
15
+ it "should create post" do
16
+ post_count = Post.count
17
+
18
+ post = Post.from_entry! @entry, nil
19
+
20
+ post.author.should == @remote_subject.actor
21
+
22
+ Post.count.should == post_count + 1
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe RemoteSubject do
4
+ describe "find_or_create_by_webfinger_uri" do
5
+ before do
6
+ @remote_subject = Factory(:remote_subject)
7
+ end
8
+
9
+ it "should find without acct:" do
10
+ RemoteSubject.find_or_create_by_webfinger_uri!(@remote_subject.webfinger_id).should == @remote_subject
11
+ end
12
+
13
+ it "should find with acct:" do
14
+ RemoteSubject.find_or_create_by_webfinger_uri!("acct:#{ @remote_subject.webfinger_id}").should == @remote_subject
15
+ end
16
+
17
+ it "should find with alias" do
18
+ splt = @remote_subject.webfinger_id.split('@')
19
+ uri = "http://#{ splt.last }/#{ splt.first }"
20
+
21
+ RemoteSubject.find_or_create_by_webfinger_uri!(uri)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe SocialStream::Ostatus::ActivityStreams do
4
+ describe "from_pshb_callback" do
5
+ before do
6
+ @entry = double("Proudhon::Entry")
7
+
8
+ @remote_subject = double("RemoteSubject")
9
+
10
+ Proudhon::Atom.should_receive(:parse) { [ @entry ] }
11
+ end
12
+
13
+ describe "with post note" do
14
+ before do
15
+ @entry.should_receive(:verb) { :post }
16
+ @entry.should_receive(:objtype) { :note }
17
+
18
+ @post = double "post"
19
+
20
+ @post.should_receive :post_activity
21
+
22
+ Post.should_receive(:from_entry!).with(@entry) { @post }
23
+ end
24
+
25
+ it "should call stubs" do
26
+ SocialStream::ActivityStreams.from_pshb_callback "test"
27
+ end
28
+ end
29
+ end
30
+ end
31
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: social_stream-ostatus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-09-05 00:00:00.000000000 Z
13
+ date: 2012-10-03 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: social_stream-base
@@ -19,7 +19,7 @@ dependencies:
19
19
  requirements:
20
20
  - - ~>
21
21
  - !ruby/object:Gem::Version
22
- version: 0.22.0
22
+ version: 0.23.0
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -27,7 +27,7 @@ dependencies:
27
27
  requirements:
28
28
  - - ~>
29
29
  - !ruby/object:Gem::Version
30
- version: 0.22.0
30
+ version: 0.23.0
31
31
  - !ruby/object:Gem::Dependency
32
32
  name: proudhon
33
33
  requirement: !ruby/object:Gem::Requirement
@@ -35,7 +35,7 @@ dependencies:
35
35
  requirements:
36
36
  - - ! '>='
37
37
  - !ruby/object:Gem::Version
38
- version: '0.3'
38
+ version: 0.3.5
39
39
  type: :runtime
40
40
  prerelease: false
41
41
  version_requirements: !ruby/object:Gem::Requirement
@@ -43,7 +43,7 @@ dependencies:
43
43
  requirements:
44
44
  - - ! '>='
45
45
  - !ruby/object:Gem::Version
46
- version: '0.3'
46
+ version: 0.3.5
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: nokogiri
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -152,25 +152,46 @@ files:
152
152
  - README.rdoc
153
153
  - Rakefile
154
154
  - app/assets/images/logos/actor/remote_subject.png
155
+ - app/assets/images/logos/contact/remote_subject.png
156
+ - app/assets/images/logos/original/remote_subject.png
157
+ - app/assets/images/logos/profile/remote_subject.png
155
158
  - app/controllers/host_meta_controller.rb
156
159
  - app/controllers/pshb_controller.rb
157
- - app/controllers/remoteusers_controller.rb
160
+ - app/controllers/remote_subjects_controller.rb
161
+ - app/controllers/salmon_controller.rb
158
162
  - app/controllers/subjects_controller.rb
159
163
  - app/controllers/webfinger_controller.rb
164
+ - app/decorators/social_stream/base/activity_decorator.rb
165
+ - app/decorators/social_stream/base/actor_decorator.rb
166
+ - app/decorators/social_stream/base/audience_decorator.rb
167
+ - app/decorators/social_stream/base/relation/custom_decorator.rb
168
+ - app/decorators/social_stream/base/tie_decorator.rb
169
+ - app/models/actor_key.rb
160
170
  - app/models/remote_subject.rb
161
- - app/views/remoteusers/index.html.erb
171
+ - app/views/remote_subjects/_show.html.erb
172
+ - app/views/remote_subjects/show.html.erb
162
173
  - config/locales/en.yml
163
174
  - config/routes.rb
164
175
  - db/migrate/20120905145030_create_social_stream_ostatus.rb
176
+ - db/migrate/20120918194708_create_actor_keys.rb
165
177
  - lib/generators/social_stream/ostatus/install_generator.rb
166
178
  - lib/generators/social_stream/ostatus/templates/initializer.rb
167
179
  - lib/social_stream-ostatus.rb
168
180
  - lib/social_stream/migrations/ostatus.rb
181
+ - lib/social_stream/ostatus/activity_streams.rb
182
+ - lib/social_stream/ostatus/controllers/debug_requests.rb
169
183
  - lib/social_stream/ostatus/engine.rb
184
+ - lib/social_stream/ostatus/models/activity.rb
170
185
  - lib/social_stream/ostatus/models/actor.rb
171
186
  - lib/social_stream/ostatus/models/audience.rb
187
+ - lib/social_stream/ostatus/models/object.rb
188
+ - lib/social_stream/ostatus/models/relation/custom.rb
189
+ - lib/social_stream/ostatus/models/tie.rb
172
190
  - lib/social_stream/ostatus/version.rb
173
191
  - social_stream-ostatus.gemspec
192
+ - spec/controllers/host_meta_controller_spec.rb
193
+ - spec/controllers/remote_subjects_controller_spec.rb
194
+ - spec/controllers/webfinger_controller_spec.rb
174
195
  - spec/dummy/Rakefile
175
196
  - spec/dummy/app/controllers/application_controller.rb
176
197
  - spec/dummy/app/helpers/application_helper.rb
@@ -202,8 +223,13 @@ files:
202
223
  - spec/dummy/public/javascripts/rails.js
203
224
  - spec/dummy/public/stylesheets/.gitkeep
204
225
  - spec/dummy/script/rails
226
+ - spec/factories/remote_subject.rb
205
227
  - spec/integration/navigation_spec.rb
228
+ - spec/models/actor_key_spec.rb
229
+ - spec/models/post_spec.rb
230
+ - spec/models/remote_subject_spec.rb
206
231
  - spec/social_stream_ostatus.spec.rb
232
+ - spec/social_stream_ostatus_activity_streams.spec.rb
207
233
  - spec/spec_helper.rb
208
234
  homepage: http://social-stream.dit.upm.es
209
235
  licenses: []
@@ -1,30 +0,0 @@
1
- class RemoteusersController < ApplicationController
2
- before_filter :authenticate_user!
3
-
4
- def index
5
- if params[:slug].present?
6
- #Selecting the remote subject
7
- u = RemoteSubject.find_or_create_using_wslug(params[:slug])
8
-
9
- #Creating the tie between me and the remote subject
10
- t = Tie.create!(:sender => current_user.actor,
11
- :receiver => u.actor,
12
- :relation_name => "friend")
13
-
14
- #Requesting a subscription to the hub
15
- t = Thread.new do
16
- uri = URI.parse(SocialStream::Ostatus.hub)
17
- response = Net::HTTP::post_form(uri,{ 'hub.callback' => pshb_callback_url,
18
- 'hub.mode' => "subscribe",
19
- 'hub.topic' => u.public_feed_url,
20
- 'hub.verify' => 'sync'})
21
-
22
- end
23
- end
24
-
25
- respond_to do |format|
26
- format.html
27
- end
28
- end
29
-
30
- end
@@ -1,8 +0,0 @@
1
- <h2> Follow remote user: </h2>
2
- <br/>
3
-
4
- <%= form_tag(add_remote_user_url, :method => "post") do %>
5
- <%= label_tag(:slug, "Remote user slug:") %>
6
- <%= text_field_tag(:slug) %>
7
- <%= submit_tag("Follow") %>
8
- <% end %>