social_stream-ostatus 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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 %>