1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
|
# Copyright 2010 Diaspora Inc.
#
# This file is part of Diaspora.
#
# Diaspora 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.
#
# Diaspora 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 Diaspora. If not, see <http://www.gnu.org/licenses/>.
#
require 'lib/diaspora/user/friending.rb'
require 'lib/diaspora/user/querying.rb'
require 'lib/salmon/salmon'
class User
include MongoMapper::Document
include Diaspora::UserModules::Friending
include Diaspora::UserModules::Querying
include Encryptor::Private
QUEUE = MessageHandler.new
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
key :username, :unique => true
key :friend_ids, Array
key :pending_request_ids, Array
key :visible_post_ids, Array
key :visible_person_ids, Array
one :person, :class_name => 'Person', :foreign_key => :owner_id
many :friends, :in => :friend_ids, :class_name => 'Person'
many :visible_people, :in => :visible_person_ids, :class_name => 'Person' # One of these needs to go
many :pending_requests, :in => :pending_request_ids, :class_name => 'Request'
many :raw_visible_posts, :in => :visible_post_ids, :class_name => 'Post'
many :aspects, :class_name => 'Aspect'
before_validation_on_create :setup_person
before_validation :do_bad_things
before_save :downcase_username
def self.find_for_authentication(conditions={})
if conditions[:username] =~ /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i # email regex
conditions[:email] = conditions.delete(:username)
else
conditions[:username].downcase!
end
super
end
######## Making things work ########
key :email, String
ensure_index :email
def method_missing(method, *args)
self.person.send(method, *args)
end
def real_name
"#{person.profile.first_name.to_s} #{person.profile.last_name.to_s}"
end
######### Aspects ######################
def aspect( opts = {} )
opts[:user] = self
Aspect.create(opts)
end
def move_friend( opts = {})
return true if opts[:to] == opts[:from]
friend = Person.first(:_id => opts[:friend_id])
if self.friend_ids.include?(friend.id)
from_aspect = self.aspect_by_id(opts[:from])
to_aspect = self.aspect_by_id(opts[:to])
if from_aspect && to_aspect
posts_to_move = from_aspect.posts.find_all_by_person_id(friend.id)
to_aspect.people << friend
to_aspect.posts << posts_to_move
from_aspect.person_ids.delete(friend.id.to_id)
posts_to_move.each{ |x| from_aspect.post_ids.delete(x.id)}
from_aspect.save
to_aspect.save
return true
end
end
false
end
######## Posting ########
def post(class_name, options = {})
if class_name == :photo
raise ArgumentError.new("No album_id given") unless options[:album_id]
aspect_ids = aspects_with_post( options[:album_id] )
aspect_ids.map!{ |aspect| aspect.id }
else
aspect_ids = options.delete(:to)
end
aspect_ids = [aspect_ids.to_s] if aspect_ids.is_a? BSON::ObjectId
raise ArgumentError.new("You must post to someone.") if aspect_ids.nil? || aspect_ids.empty?
post = build_post(class_name, options)
post.socket_to_uid(id, :aspect_ids => aspect_ids) if post.respond_to?(:socket_to_uid)
push_to_aspects(post, aspect_ids)
post
end
def build_post( class_name, options = {})
options[:person] = self.person
model_class = class_name.to_s.camelize.constantize
post = model_class.instantiate(options)
post.save
self.raw_visible_posts << post
self.save
post
end
def push_to_aspects( post, aspect_ids )
if aspect_ids == :all || aspect_ids == "all"
aspects = self.aspects
elsif aspect_ids.is_a?(Array) && aspect_ids.first.class == Aspect
aspects = aspect_ids
else
aspects = self.aspects.find_all_by_id( aspect_ids )
end
#send to the aspects
target_people = []
aspects.each{ |aspect|
aspect.posts << post
aspect.save
target_people = target_people | aspect.people
}
push_to_people(post, target_people)
end
def push_to_people(post, people)
people.each{|person|
salmon(post, :to => person)
}
end
def push_to_person( person, xml )
Rails.logger.debug("Adding xml for #{self} to message queue to #{url}")
QUEUE.add_post_request( person.receive_url, person.encrypt(xml) )
QUEUE.process
end
def salmon( post, opts = {} )
salmon = Salmon::SalmonSlap.create(self, post.to_diaspora_xml)
push_to_person( opts[:to], salmon.to_xml)
salmon
end
######## Commenting ########
def comment(text, options = {})
comment = build_comment(text, options)
if comment
dispatch_comment comment
comment.socket_to_uid id
end
comment
end
def build_comment( text, options = {})
raise "must comment on something!" unless options[:on]
comment = Comment.new(:person_id => self.person.id, :text => text, :post => options[:on])
comment.creator_signature = comment.sign_with_key(encryption_key)
if comment.save
comment
else
Rails.logger.warn "this failed to save: #{comment.inspect}"
false
end
end
def dispatch_comment( comment )
if owns? comment.post
comment.post_creator_signature = comment.sign_with_key(encryption_key)
comment.save
push_to_people comment, people_in_aspects(aspects_with_post(comment.post.id))
elsif owns? comment
comment.save
salmon comment, :to => comment.post.person
end
end
######### Posts and Such ###############
def retract( post )
aspect_ids = aspects_with_post( post.id )
aspect_ids.map!{|aspect| aspect.id.to_s}
post.unsocket_from_uid(self.id, :aspect_ids => aspect_ids) if post.respond_to? :unsocket_from_uid
retraction = Retraction.for(post)
push_to_people retraction, people_in_aspects(aspects_with_post(post.id))
retraction
end
########### Profile ######################
def update_profile(params)
if self.person.update_attributes(params)
push_to_aspects profile, :all
true
else
false
end
end
###### Receiving #######
def receive_salmon ciphertext
cleartext = decrypt( ciphertext)
Rails.logger.info("Received a salmon: #{cleartext}")
salmon = Salmon::SalmonSlap.parse cleartext
if salmon.verified_for_key?(salmon.author.public_key)
Rails.logger.info("data in salmon: #{salmon.data}")
self.receive(salmon.data)
end
end
def receive xml
object = Diaspora::Parser.from_xml(xml)
Rails.logger.debug("Receiving object for #{self.real_name}:\n#{object.inspect}")
Rails.logger.debug("From: #{object.person.inspect}") if object.person
if object.is_a? Retraction
if object.type == 'Person'
Rails.logger.info( "the person id is #{object.post_id} the friend found is #{visible_person_by_id(object.post_id).inspect}")
unfriended_by visible_person_by_id(object.post_id)
else
object.perform self.id
aspects = self.aspects_with_person(object.person)
aspects.each{ |aspect| aspect.post_ids.delete(object.post_id.to_id)
aspect.save
}
end
elsif object.is_a? Request
person = Diaspora::Parser.parse_or_find_person_from_xml( xml )
person.serialized_key ||= object.exported_key
object.person = person
object.person.save
old_request = Request.first(:id => object.id)
object.aspect_id = old_request.aspect_id if old_request
object.save
receive_friend_request(object)
elsif object.is_a? Profile
person = Diaspora::Parser.owner_id_from_xml xml
person.profile = object
person.save
elsif object.is_a?(Comment)
object.person = Diaspora::Parser.parse_or_find_person_from_xml( xml ).save if object.person.nil?
self.visible_people = self.visible_people | [object.person]
self.save
Rails.logger.debug("The person parsed from comment xml is #{object.person.inspect}") unless object.person.nil?
object.person.save
Rails.logger.debug("From: #{object.person.inspect}") if object.person
raise "In receive for #{self.real_name}, signature was not valid on: #{object.inspect}" unless object.post.person == self.person || object.verify_post_creator_signature
object.save
unless owns?(object)
dispatch_comment object
end
object.socket_to_uid(id) if (object.respond_to?(:socket_to_uid) && !self.owns?(object))
else
Rails.logger.debug("Saving object: #{object}")
object.user_refs += 1
object.save
self.raw_visible_posts << object
self.save
aspects = self.aspects_with_person(object.person)
aspects.each{ |aspect|
aspect.posts << object
aspect.save
object.socket_to_uid(id, :aspect_ids => [aspect.id]) if (object.respond_to?(:socket_to_uid) && !self.owns?(object))
}
end
end
###Helpers############
def self.instantiate!( opts = {} )
opts[:person][:email] = opts[:email]
opts[:person][:serialized_key] = generate_key
User.create!( opts)
end
def terse_url
terse = self.url.gsub(/(https?:|www\.)\/\//, '')
terse = terse.chop! if terse[-1, 1] == '/'
terse
end
def diaspora_handle
"#{self.username}@#{self.terse_url}"
end
def do_bad_things
self.password_confirmation = self.password
end
def setup_person
self.person.serialized_key ||= User.generate_key.export
self.person.email ||= email
self.person.save!
end
def downcase_username
username.downcase!
end
def as_json(opts={})
{
:user => {
:posts => self.raw_visible_posts.each{|post| post.as_json},
:friends => self.friends.each {|friend| friend.as_json},
:aspects => self.aspects.each {|aspect| aspect.as_json},
:pending_requests => self.pending_requests.each{|request| request.as_json},
}
}
end
def self.generate_key
OpenSSL::PKey::RSA::generate 4096
end
end
|