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
|
# Copyright (C) 2007, 2008, 2009, 2010 Heiko Bernloehr (FreeIT.de).
#
# This file is part of ECS.
#
# ECS 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.
#
# ECS 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 ECS. If not, see <http://www.gnu.org/licenses/>.
class Message < ActiveRecord::Base
require 'exceptions'
require 'json/add/rails'
has_many :memberships, :through => :membership_messages
has_many :membership_messages
has_many :events, :dependent => :destroy
has_many :community_messages, :dependent => :destroy
has_many :communities, :through => :community_messages
has_one :auth, :dependent => :destroy
belongs_to :ressource
named_scope :for_participant_receiver, lambda {|participant| {
:joins => {:membership_messages => {:membership => :participant}},
:order => "id ASC",
:conditions => {:participants => {:id => participant.id}}}}
named_scope :for_participant_sender, lambda {|participant| {
:order => "id ASC",
:conditions => {:sender => participant.id}}}
named_scope :for_not_removed, lambda { {
:order => "id ASC",
:conditions => {:removed => false}}}
named_scope :for_removed, lambda { {
:order => "id ASC",
:conditions => {:removed => true}}}
named_scope :for_resource, lambda {|namespace, name| {
:joins => :ressource,
:order => "id ASC",
:conditions => {:ressources => {:namespace => namespace, :ressource => name}}}}
def self.create__(request, app_namespace, ressource_name, participant)
transaction do
message = create! do |arm|
arm.create_update_helper(request, app_namespace, ressource_name, participant.id)
end
MembershipMessage.extract_x_ecs_receiver_communities(request.headers["X-EcsReceiverCommunities"]).each do |cid|
message.communities << Community.find(cid)
end
MembershipMessage.populate_jointable(message,
request.headers["X-EcsReceiverMemberships"],
request.headers["X-EcsReceiverCommunities"],
participant)
Participant.for_message(message).uniq.each do |p|
Event.make(:event_type_name => EvType.find(1).name, :participant => p, :message => message)
end if message.ressource.events
if app_namespace == 'sys' and ressource_name == 'auths'
message.post_create_auths_resource(participant)
end
message
end
rescue ActiveRecord::RecordInvalid
raise Ecs::InvalidMessageException, $!.to_s
end
def update__(request, app_namespace, ressource_name, participant)
raise(Ecs::AuthorizationException, "You are not the original sender of the message.") unless participant.sender?(self)
transaction do
create_update_helper(request, app_namespace, ressource_name, participant.id)
save!
receivers_old = Participant.for_message(self).uniq
MembershipMessage.de_populate_jointable(self)
MembershipMessage.populate_jointable(self,
request.headers["X-EcsReceiverMemberships"],
request.headers["X-EcsReceiverCommunities"],
participant)
receivers_new = Participant.for_message(self).uniq
# TODO: if there are only the headers X-EcsReceiverMemberships and
# X-EcsReceiverCommunities are updated, then we have to generate events only
# for these new and removed receivers. To distinguish if the message body
# is untouched we can use the ETag functionality.
(receivers_new & receivers_old).each do |p|
# generate updated events
Event.make(:event_type_name => EvType.find(3).name, :participant => p, :message => self)
end if self.ressource.events
(receivers_old - receivers_new).each do |p|
# generate destroyed events
Event.make(:event_type_name => EvType.find(2).name, :participant => p, :message => self)
end if self.ressource.events
(receivers_new - receivers_old).each do |p|
# generate created events
Event.make(:event_type_name => EvType.find(1).name, :participant => p, :message => self)
end if self.ressource.events
if app_namespace == 'sys' and ressource_name == 'auths'
post_create_auths_resource(participant)
end
self
end
rescue ActiveRecord::RecordInvalid
raise Ecs::InvalidMessageException, $!.to_s
end
def validate
if content_type.blank? then
errors.add_to_base("*** You must povide a \"Content-Type\" header. ")
end
if body.blank? then
errors.add_to_base("*** You have to provide a \"http body\". *** ")
end
if sender.blank? then
errors.add_to_base("*** There is no \"sender\"; this is a fatal error; please report this to ecs@freeit.de. *** ")
end
end
# return first messages from fifo/lifo queue
def self.fifo_lifo_rest(namespace, ressource, participant_id, options={:queue_type => :fifo})
find(:first, :readonly => false, :lock => true,
:joins => [:ressource, { :membership_messages => { :membership => :participant } }],
:conditions => { :participants => { :id => participant_id },
:ressources => { :namespace => namespace, :ressource => ressource } },
:order => :messages.to_s+".id #{(options[:queue_type]==:fifo)?'ASC':'DESC'}")
end
# get a record out of the message table
def self.get_record(msg_id, app_namespace, ressource_name)
outdated_auth_token = nil
ressource = Ressource.find_by_namespace_and_ressource(app_namespace, ressource_name)
raise(Ecs::InvalidRessourceUriException, "*** ressource uri error ***") unless ressource
if app_namespace == 'sys' and ressource_name == 'auths'
# processing a auths resource
if msg_id =~ /\D/
# asking a one touch token with the hash key
auth = Auth.find_by_one_touch_hash(msg_id)
if auth
record = auth.message
else
raise ActiveRecord::RecordNotFound, "Invalid auths hash"
end
else
unless record = find_by_id_and_ressource_id(msg_id.to_i, ressource.id)
raise ActiveRecord::RecordNotFound, "Invalid auths id"
end
end
else
record = find_by_id_and_ressource_id(msg_id.to_i, ressource.id)
end
if !record or record.removed
raise ActiveRecord::RecordNotFound, "Invalid resource id"
else
[record, outdated_auth_token]
end
end
def filter(action_name, app_namespace, ressource_name, params)
d="filter/#{app_namespace}/#{ressource_name}/#{action_name}/*"
filters=Dir[d].collect{|f| File.directory?(f) ? f : nil}.compact
return if filters.empty?
FILTER_API.params= params
FILTER_API.record= self
filters.sort!
filters.each do |f|
files= Dir[f+'/*.rb']
next if files.empty?
EcsFilter.constants.each {|c| EcsFilter.instance_eval { remove_const c.to_sym } }
files.each do |e|
EcsFilter.module_eval IO.read(e)
end
eval "EcsFilter::Filter.start"
end
rescue Exception
logger.error "Filter Exception: "+$!.class.to_s+": "+$!.backtrace[0]
logger.error "Filter Exception: "+$!.message
end
# Request body has to be in json format.
# Preprocess request body if it's a /sys/auths resource.
# Generate a one touch token (hash)
def post_create_auths_resource(participant)
ttl_min = 5.seconds
ttl = ttl_min + 60.seconds
unless Mime::Type.lookup(self.content_type).to_sym == :json
raise Ecs::InvalidMimetypeException, "Body format has to be in JSON"
end
begin
b = JSON.parse(self.body)
rescue JSON::ParserError
raise Ecs::InvalidMessageException, "Invalid JSON body"
end
bks = b.keys
# NOTE Assures that there are at least url or realm set -> backward compatibility
unless bks.include?("url") or bks.include?("realm")
raise Ecs::InvalidMessageException, "You have to provide realm or url attribute"
end
#msg_id = URI.split(b["url"])[5][1..-1].sub(/[^\/]*\/[^\/]*\/(.*)/, '\1').to_i
#begin
# Message.find(msg_id)
#rescue ActiveRecord::RecordNotFound
# raise Ecs::InvalidMessageException, $!.to_s
#end
case
when (!bks.include?("sov") and !bks.include?("eov"))
b["sov"] = Time.now.xmlschema
b["eov"] = (Time.now + ttl).xmlschema
when (bks.include?("sov") and !bks.include?("eov"))
if Time.parse(b["sov"]) < Time.now
raise Ecs::InvalidMessageException, 'sov time is younger then current time'
end
b["eov"] = (Time.parse(b["sov"]) + ttl).xmlschema
when (!bks.include?("sov") and bks.include?("eov"))
if Time.parse(b["eov"]) < (Time.now + ttl_min)
raise Ecs::InvalidMessageException, 'eov time is too young'
end
b["sov"] = Time.now.xmlschema
when (bks.include?("sov") and bks.include?("eov"))
if (Time.parse(b["eov"]) < Time.now) or (Time.parse(b["eov"]) < Time.parse(b["sov"]))
raise Ecs::InvalidMessageException, 'invalid times either in sov or eov'
end
end
b["abbr"] = participant.organization.abrev
one_touch_token_hash = Digest::SHA1.hexdigest(rand.to_s+Time.now.to_s)
b["hash"] = one_touch_token_hash
b["pid"] = participant.id
self.body = JSON.pretty_generate(b)
self.auth = Auth.new :one_touch_hash => one_touch_token_hash
save!
self
end
# If the record has zero relations to memberships and is not tagged for
# postrouting it will be deleted.
def destroy_as_receiver(participant=nil)
memberships= Membership.receiver(participant.id, self.id)
if memberships.empty?
raise Ecs::NoReceiverOfMessageException,
"you are not a receiver of " +
"\"#{self.ressource.namespace}/#{self.ressource.ressource}/#{self.id.to_s}\""
end
if participant
MembershipMessage.delete_relations(self, memberships)
end
destroy_or_tag_as_removed if membership_messages.blank? and !ressource.postroute
end
alias destroy_unlinked_and_not_postrouted destroy_as_receiver
# Delete a message and send appropriate events. It will only be "fully"
# deleted when there are no references from any events otherwise it will be
# tagged as deleted.
def destroy_as_sender
participants = Participant.for_message(self).uniq
participants.each do |participant|
Event.make(:event_type_name => EvType.find(2).name, :participant => participant, :message => self)
end if ressource.events
MembershipMessage.delete_relations(self)
destroy_or_tag_as_removed
end
alias destroy_ destroy_as_sender
def outtimed_auths_resource_by_non_owner?(app_namespace, resource_name, participant)
memberships= Membership.receiver(participant.id, self.id)
app_namespace == 'sys' and
resource_name == 'auths' and
!memberships.empty? and
!participant.sender?(self) and
!auth.test_validation_window
end
def valid_auths_resource_fetched_by_non_owner?(app_namespace, resource_name, memberships, participant)
app_namespace == 'sys' and
resource_name == 'auths' and
!memberships.empty? and
!participant.sender?(self) and
auth.test_validation_window
end
def valid_no_auths_resource_fetched_by_non_owner?(app_namespace, resource_name, memberships, participant)
app_namespace != 'sys' and
ressource_name != 'auths' and
!memberships.empty? and
!participant.sender?(self)
end
# Helper function for create and update
def create_update_helper(request, app_namespace, ressource_name, participant_id)
ressource = Ressource.find_by_namespace_and_ressource(app_namespace, ressource_name)
raise(Ecs::InvalidRessourceUriException, "*** ressource uri error ***") unless ressource
self.ressource_id = ressource.id
self.content_type = request.headers["CONTENT_TYPE"]
self.sender = participant_id
self.body = request.raw_post
end
private
# Deletes the message if there are no references from events otherwise it
# will be tagged as deleted.
def destroy_or_tag_as_removed
if self.events.blank?
destroy
else
self.removed = true
save!
end
end
end
|