Merge branch 'salmon-refactor'

This commit is contained in:
Raphael 2010-10-05 09:47:10 -07:00
commit bb88a6fb8c
11 changed files with 176 additions and 95 deletions

View file

@ -166,26 +166,28 @@ class User
aspect.save aspect.save
target_people = target_people | aspect.people target_people = target_people | aspect.people
} }
push_to_people(post, target_people) push_to_people(post, target_people)
end end
def push_to_people(post, people) def push_to_people(post, people)
salmon = salmon(post)
people.each{|person| people.each{|person|
salmon(post, :to => person) xml = salmon.xml_for person
push_to_person( person, xml)
} }
end end
def push_to_person( person, xml ) def push_to_person( person, xml )
Rails.logger.debug("Adding xml for #{self} to message queue to #{url}") Rails.logger.debug("Adding xml for #{self} to message queue to #{url}")
QUEUE.add_post_request( person.receive_url, person.encrypt(xml) ) QUEUE.add_post_request( person.receive_url, xml )
QUEUE.process QUEUE.process
end end
def salmon( post, opts = {} ) def salmon( post )
salmon = Salmon::SalmonSlap.create(self, post.to_diaspora_xml) created_salmon = Salmon::SalmonSlap.create(self, post.to_diaspora_xml)
push_to_person( opts[:to], salmon.to_xml) created_salmon
salmon
end end
######## Commenting ######## ######## Commenting ########
@ -217,7 +219,7 @@ class User
push_to_people comment, people_in_aspects(aspects_with_post(comment.post.id)) push_to_people comment, people_in_aspects(aspects_with_post(comment.post.id))
elsif owns? comment elsif owns? comment
comment.save comment.save
salmon comment, :to => comment.post.person push_to_people comment, [comment.post.person]
end end
end end

View file

@ -6,7 +6,7 @@ cross_server:
deploy_to: '/usr/local/app/diaspora' deploy_to: '/usr/local/app/diaspora'
user: 'root' user: 'root'
repo: 'git://github.com/diaspora/diaspora.git' repo: 'git://github.com/diaspora/diaspora.git'
branch: 'master' branch: 'salmon_refactor'
default_env: 'development' default_env: 'development'
servers: servers:
tom: tom:

View file

@ -22,7 +22,7 @@ module Diaspora
aspect.requests << request aspect.requests << request
aspect.save aspect.save
salmon request, :to => desired_friend push_to_people request, [desired_friend]
end end
request request
end end
@ -80,7 +80,7 @@ module Diaspora
def unfriend(bad_friend) def unfriend(bad_friend)
Rails.logger.info("#{self.real_name} is unfriending #{bad_friend.inspect}") Rails.logger.info("#{self.real_name} is unfriending #{bad_friend.inspect}")
retraction = Retraction.for(self) retraction = Retraction.for(self)
salmon( retraction, :to => bad_friend) push_to_people retraction, [bad_friend]
remove_friend(bad_friend) remove_friend(bad_friend)
end end

View file

@ -1,12 +1,11 @@
module Diaspora module Diaspora
module UserModules module UserModules
module Receiving module Receiving
def receive_salmon ciphertext def receive_salmon salmon_xml
cleartext = decrypt( ciphertext) salmon = Salmon::SalmonSlap.parse salmon_xml, self
salmon = Salmon::SalmonSlap.parse cleartext
if salmon.verified_for_key?(salmon.author.public_key) if salmon.verified_for_key?(salmon.author.public_key)
Rails.logger.info("data in salmon: #{salmon.data}") Rails.logger.info("data in salmon: #{salmon.parsed_data}")
self.receive(salmon.data) self.receive(salmon.parsed_data)
end end
end end

View file

@ -41,16 +41,37 @@ end
module Salmon module Salmon
class SalmonSlap class SalmonSlap
attr_accessor :magic_sig, :author, :author_email, :data, :data_type, :sig attr_accessor :magic_sig, :author, :author_email, :aes_key, :iv, :parsed_data,
def self.parse(xml) :data_type, :sig
def self.create(user, activity)
salmon = self.new
salmon.author = user.person
aes_key_hash = user.person.gen_aes_key
salmon.aes_key = aes_key_hash['key']
salmon.iv = aes_key_hash['iv']
salmon.magic_sig = MagicSigEnvelope.create(user , user.person.aes_encrypt(activity, aes_key_hash))
salmon
end
def self.parse(xml, user)
slap = self.new slap = self.new
doc = Nokogiri::XML(xml) doc = Nokogiri::XML(xml)
sig_doc = doc.search('entry') sig_doc = doc.search('entry')
### Header ##
decrypted_header = user.decrypt(doc.search('encrypted_header').text)
header_doc = Nokogiri::XML(decrypted_header)
slap.aes_key = header_doc.search('aes_key').text
slap.iv = header_doc.search('iv').text
slap.magic_sig = MagicSigEnvelope.parse sig_doc slap.magic_sig = MagicSigEnvelope.parse sig_doc
if 'base64url' == slap.magic_sig.encoding if 'base64url' == slap.magic_sig.encoding
slap.data = decode64url(slap.magic_sig.data)
key_hash = {'key' => slap.aes_key, 'iv' => slap.iv}
slap.parsed_data = user.aes_decrypt(decode64url(slap.magic_sig.data), key_hash)
slap.sig = slap.magic_sig.sig slap.sig = slap.magic_sig.sig
else else
raise ArgumentError, "Magic Signature data must be encoded with base64url, was #{slap.magic_sig.encoding}" raise ArgumentError, "Magic Signature data must be encoded with base64url, was #{slap.magic_sig.encoding}"
@ -65,17 +86,11 @@ module Salmon
slap slap
end end
def self.create(user, activity) def xml_for person
salmon = self.new
salmon.author = user.person
salmon.magic_sig = MagicSigEnvelope.create(user , activity)
salmon
end
def to_xml
xml =<<ENTRY xml =<<ENTRY
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<entry xmlns='http://www.w3.org/2005/Atom'> <entry xmlns='http://www.w3.org/2005/Atom'>
<encrypted_header>#{person.encrypt(decrypted_header)}</encrypted_header>
<author> <author>
<name>#{@author.real_name}</name> <name>#{@author.real_name}</name>
<uri>acct:#{@author.diaspora_handle}</uri> <uri>acct:#{@author.diaspora_handle}</uri>
@ -86,6 +101,19 @@ ENTRY
end end
def decrypted_header
header =<<HEADER
<decrypted_header>
<iv>#{iv}</iv>
<aes_key>#{aes_key}</aes_key>
<author>
<name>#{@author.real_name}</name>
<uri>acct:#{@author.diaspora_handle}</uri>
</author>
</decrypted_header>
HEADER
end
def author def author
if @author if @author
@author @author

View file

@ -65,4 +65,17 @@ namespace :db do
} }
puts "everything should be peachy" puts "everything should be peachy"
end end
task :move_private_key do
User.all.each do |user|
if user.private_key.nil?
user.private_key = user.person.serialized_key
user.save
person = user.person
person.serialized_key = nil
person.serialized_public_key = user.encryption_key.public_key
person.save
end
end
end
end end

View file

@ -19,7 +19,6 @@ namespace :generate do
Rails.application.config.secret_token = '#{secret}' Rails.application.config.secret_token = '#{secret}'
EOF EOF
puts "YAY!!"
end end
end end

View file

@ -5,31 +5,32 @@
require 'spec_helper' require 'spec_helper'
describe PublicsController do describe PublicsController do
render_views render_views
let(:user) {Factory.create :user}
let(:user2){Factory.create :user}
before do before do
@user = Factory.create(:user) sign_in :user, user
sign_in :user, @user
end end
describe 'receive endpoint' do describe 'receive endpoint' do
it 'should have a and endpoint and return a 200 on successful receipt of a request' do it 'should have a and endpoint and return a 200 on successful receipt of a request' do
post :receive, :id =>@user.person.id post :receive, :id =>user.person.id
response.code.should == '200' response.code.should == '200'
end end
it 'should accept a post from another node and save the information' do it 'should accept a post from another node and save the information' do
user2 = Factory.create(:user)
message = user2.build_post(:status_message, :message => "hi") message = user2.build_post(:status_message, :message => "hi")
@user.reload user.reload
@user.visible_post_ids.include?(message.id).should be false user.visible_post_ids.include?(message.id).should be false
xml = @user.person.encrypt(user2.salmon(message, :to => @user.person).to_xml)
xml = user2.salmon(message).xml_for(user.person)
post :receive, :id => @user.person.id, :xml => xml post :receive, :id => user.person.id, :xml => xml
@user.reload user.reload
@user.visible_post_ids.include?(message.id).should be true user.visible_post_ids.include?(message.id).should be true
end end
end end
@ -41,35 +42,29 @@ describe PublicsController do
end end
describe 'friend requests' do describe 'friend requests' do
let(:aspect2) {user2.aspect(:name => 'disciples')}
let!(:req) {user2.send_friend_request_to(user.person, aspect2)}
let!(:xml) {user2.salmon(req).xml_for(user.person)}
before do before do
@user2 = Factory.create(:user)
aspect = @user2.aspect(:name => 'disciples')
@user3 = Factory.create(:user)
req = @user2.send_friend_request_to(@user.person, aspect)
@xml = @user.person.encrypt(@user2.salmon(req, :to => @user.person).to_xml)
req.delete req.delete
@user2.reload user2.reload
@user2.pending_requests.count.should be 1 user2.pending_requests.count.should be 1
end end
it 'should add the pending request to the right user if the target person exists locally' do it 'should add the pending request to the right user if the target person exists locally' do
@user2.delete user2.delete
post :receive, :id => @user.person.id, :xml => @xml post :receive, :id => user.person.id, :xml => xml
assigns(:user).should eq(@user) assigns(:user).should eq(user)
end end
it 'should add the pending request to the right user if the target person does not exist locally' do it 'should add the pending request to the right user if the target person does not exist locally' do
Person.should_receive(:by_webfinger).with(@user2.person.diaspora_handle).and_return(@user2.person) Person.should_receive(:by_webfinger).with(user2.person.diaspora_handle).and_return(user2.person)
@user2.person.delete user2.person.delete
@user2.delete user2.delete
post :receive, :id => @user.person.id, :xml => @xml post :receive, :id => user.person.id, :xml => xml
assigns(:user).should eq(@user) assigns(:user).should eq(user)
end end
end end
end end

View file

@ -5,52 +5,97 @@
require 'spec_helper' require 'spec_helper'
describe Salmon do describe Salmon do
before do let(:user){Factory.create :user}
let(:user2) {Factory.create :user}
let(:user3) {Factory.create :user}
let(:post){ user.post :status_message, :message => "hi", :to => user.aspect(:name => "sdg").id }
@user = Factory.create :user let!(:created_salmon) {Salmon::SalmonSlap.create(user, post.to_diaspora_xml)}
@post = @user.post :status_message, :message => "hi", :to => @user.aspect(:name => "sdg").id
@sent_salmon = Salmon::SalmonSlap.create(@user, @post.to_diaspora_xml) describe '#create' do
@parsed_salmon = Salmon::SalmonSlap.parse @sent_salmon.to_xml
stub_success("tom@tom.joindiaspora.com") it 'has data in the magic envelope' do
created_salmon.magic_sig.data.should_not be nil
end
it 'has no parsed_data' do
created_salmon.parsed_data.should be nil
end
it 'sets aes and iv key' do
created_salmon.aes_key.should_not be nil
created_salmon.iv.should_not be nil
end
it 'makes the data in the signature encrypted with that key' do
key_hash = {'key' => created_salmon.aes_key, 'iv' => created_salmon.iv}
decoded_string = Salmon::SalmonSlap.decode64url(created_salmon.magic_sig.data)
user.aes_decrypt(decoded_string, key_hash).should == post.to_diaspora_xml
end
end
describe '#xml_for' do
let(:xml) {created_salmon.xml_for user2.person}
it 'has a encrypted header field' do
xml.include?("encrypted_header").should be true
end
it 'the encrypted_header field should contain the aes key' do
doc = Nokogiri::XML(xml)
decrypted_header = user2.decrypt(doc.search('encrypted_header').text)
decrypted_header.include?(created_salmon.aes_key).should be true
end
end end
it 'should verify the signature on a roundtrip' do context 'marshaling' do
let(:xml) {created_salmon.xml_for user2.person}
let(:parsed_salmon) { Salmon::SalmonSlap.parse(xml, user2)}
@sent_salmon.magic_sig.data.should == @parsed_salmon.magic_sig.data it 'should parse out the aes key' do
parsed_salmon.aes_key.should == created_salmon.aes_key
end
@sent_salmon.magic_sig.sig.should == @parsed_salmon.magic_sig.sig it 'should parse out the iv' do
@sent_salmon.magic_sig.signable_string.should == @parsed_salmon.magic_sig.signable_string parsed_salmon.iv.should == created_salmon.iv
end
it 'should parse out the authors diaspora_handle' do
parsed_salmon.author_email.should == user.person.diaspora_handle
@parsed_salmon.verified_for_key?(OpenSSL::PKey::RSA.new(@user.exported_key)).should be true end
@sent_salmon.verified_for_key?(OpenSSL::PKey::RSA.new(@user.exported_key)).should be true
end
it 'should return the data so it can be "received"' do describe '#author' do
before do
stub_success("tom@tom.joindiaspora.com")
end
xml = @post.to_diaspora_xml it 'should reference a local author' do
parsed_salmon.author.should == user.person
end
@parsed_salmon.data.should == xml it 'should reference a remote author' do
end parsed_salmon.author_email = 'tom@tom.joindiaspora.com'
parsed_salmon.author.public_key.should_not be_nil
end
it 'should parse out the authors diaspora_handle' do it 'should fail to reference a nonexistent remote author' do
@parsed_salmon.author_email.should == @user.person.diaspora_handle parsed_salmon.author_email = 'idsfug@difgubhpsduh.rgd'
proc {
Redfinger.stub(:finger).and_return(nil) #Redfinger returns nil when there is no profile
parsed_salmon.author.real_name}.should raise_error /No webfinger profile found/
end
end
it 'verifies the signature for the sender' do
parsed_salmon.verified_for_key?(user.public_key).should be true
end
it 'contains the original data' do
parsed_salmon.parsed_data.should == post.to_diaspora_xml
end
end end
it 'should reference a local author' do
@parsed_salmon.author.should == @user.person
end
it 'should reference a remote author' do
@parsed_salmon.author_email = 'tom@tom.joindiaspora.com'
@parsed_salmon.author.public_key.should_not be_nil
end
it 'should fail to reference a nonexistent remote author' do
@parsed_salmon.author_email = 'idsfug@difgubhpsduh.rgd'
proc {
Redfinger.stub(:finger).and_return(nil) #Redfinger returns nil when there is no profile
@parsed_salmon.author.real_name}.should raise_error /No webfinger profile found/
end
end end

View file

@ -71,19 +71,19 @@ describe User do
describe '#push_to_aspects' do describe '#push_to_aspects' do
it 'should push a post to a aspect' do it 'should push a post to a aspect' do
user.should_receive(:salmon).twice user.should_receive(:push_to_person).twice
user.push_to_aspects(post, aspect.id) user.push_to_aspects(post, aspect.id)
end end
it 'should push a post to all aspects' do it 'should push a post to all aspects' do
user.should_receive(:salmon).exactly(3).times user.should_receive(:push_to_person).exactly(3).times
user.push_to_aspects(post, :all) user.push_to_aspects(post, :all)
end end
end end
describe '#push_to_people' do describe '#push_to_people' do
it 'should push to people' do it 'should push to people' do
user.should_receive(:salmon).twice user.should_receive(:push_to_person).twice
user.push_to_people(post, [user2.person, user3.person]) user.push_to_people(post, [user2.person, user3.person])
end end
end end

View file

@ -173,11 +173,11 @@ describe User do
describe 'salmon' do describe 'salmon' do
before do before do
@post = @user.post :status_message, :message => "hello", :to => @aspect.id @post = @user.post :status_message, :message => "hello", :to => @aspect.id
@salmon = @user.salmon( @post, :to => @user2.person ) @salmon = @user.salmon( @post )
end end
it 'should receive a salmon for a post' do it 'should receive a salmon for a post' do
@user2.receive_salmon( @user2.person.encrypt(@salmon.to_xml) ) @user2.receive_salmon( @salmon.xml_for @user2.person )
@user2.visible_post_ids.include?(@post.id).should be true @user2.visible_post_ids.include?(@post.id).should be true
end end
end end