diff options
author | Heiko Bernloehr <Heiko.Bernloehr@FreeIT.de> | 2017-03-01 22:28:20 +0100 |
---|---|---|
committer | Heiko Bernloehr <Heiko.Bernloehr@FreeIT.de> | 2017-03-01 22:28:20 +0100 |
commit | f346740451f931acbfce34be450b9d0499cabc70 (patch) | |
tree | 918b821c56bc372a47404cff0f0cd49081c40f4f | |
parent | 8d7481a2502ec136233c9d3a6eb404d428920bf0 (diff) | |
download | ecs-f346740451f931acbfce34be450b9d0499cabc70.tar.gz ecs-f346740451f931acbfce34be450b9d0499cabc70.zip |
Initial models.
-rw-r--r-- | app/models/auth.rb | 57 | ||||
-rw-r--r-- | app/models/community.rb | 36 | ||||
-rw-r--r-- | app/models/community_message.rb | 22 | ||||
-rw-r--r-- | app/models/ev_type.rb | 21 | ||||
-rw-r--r-- | app/models/event.rb | 94 | ||||
-rw-r--r-- | app/models/identity.rb | 36 | ||||
-rw-r--r-- | app/models/logging_observer.rb | 15 | ||||
-rw-r--r-- | app/models/membership.rb | 120 | ||||
-rw-r--r-- | app/models/membership_message.rb | 126 | ||||
-rw-r--r-- | app/models/message.rb | 324 | ||||
-rw-r--r-- | app/models/organization.rb | 32 | ||||
-rw-r--r-- | app/models/participant.rb | 169 | ||||
-rw-r--r-- | app/models/ressource.rb | 50 | ||||
-rw-r--r-- | app/models/subparticipant.rb | 137 |
14 files changed, 1239 insertions, 0 deletions
diff --git a/app/models/auth.rb b/app/models/auth.rb new file mode 100644 index 0000000..ca52bf6 --- /dev/null +++ b/app/models/auth.rb @@ -0,0 +1,57 @@ +class Auth < ActiveRecord::Base + belongs_to :message + + #named_scope :hash, lambda {|hash| { + # :joins => {:membership_messages => {:membership => :participant}}, + # :order => "id ASC", + # :conditions => {:participants => {:id => participant.id}}}} + + + # if valid time window return true + def test_validation_window + b = JSON.parse(message.body) + sov = Time.parse(b["sov"]) + eov = Time.parse(b["eov"]) + if eov > Time.now + true + else + false + end + end + + # garbage collect outtimed authorization tokens + def self.gc_outtimed + gc_sys_auths_lock= "#{Rails.root}/tmp/gc_sys_auths.lock" + if File.exists?(gc_sys_auths_lock) + logtext= "GC: there seems to be already running a ecs:gc_sys_auths process (#{gc_sys_auths_lock}). Aborting." + logger.info logtext + puts logtext unless Rails.env.test? + else + begin + File.open(gc_sys_auths_lock,"w") do |f| + f.puts "#{Process.pid}" + end + logtext= "GC: Searching for outtimed auths ..." + logger.info logtext + puts logtext unless Rails.env.test? + Auth.all.each do |auth| + if ! auth.test_validation_window + auth.message.destroy_as_sender + logtext= "GC: garbage collect auths token: #{auth.one_touch_hash}" + logger.info logtext + puts logtext unless Rails.env.test? + end + end + logtext= "GC: Searching for outtimed auths done." + logger.info logtext + puts logtext unless Rails.env.test? + ensure + begin + File.delete(gc_sys_auths_lock) + rescue + end + end + end + end + +end diff --git a/app/models/community.rb b/app/models/community.rb new file mode 100644 index 0000000..2cd3298 --- /dev/null +++ b/app/models/community.rb @@ -0,0 +1,36 @@ +# 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 Community < ActiveRecord::Base + has_many :memberships, :order => :id + has_many :participants, :through => :memberships + has_many :community_messages, :dependent => :destroy + has_many :messages, :through => :community_messages + validates_presence_of :name + validates_uniqueness_of :name + + named_scope :for_participant, lambda { |participant| { + :joins => [:memberships => :participant], + :conditions => { :participants => { :id => participant.id }}}} + + named_scope :for_message, lambda { |message| { + :joins => [:memberships => {:membership_messages => :message}], + :conditions => { :messages => { :id => message.id }}}} + + +end diff --git a/app/models/community_message.rb b/app/models/community_message.rb new file mode 100644 index 0000000..b0e6033 --- /dev/null +++ b/app/models/community_message.rb @@ -0,0 +1,22 @@ +# 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 CommunityMessage < ActiveRecord::Base + belongs_to :message + belongs_to :community +end diff --git a/app/models/ev_type.rb b/app/models/ev_type.rb new file mode 100644 index 0000000..87c87ba --- /dev/null +++ b/app/models/ev_type.rb @@ -0,0 +1,21 @@ +# 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 EvType < ActiveRecord::Base + has_many :events +end diff --git a/app/models/event.rb b/app/models/event.rb new file mode 100644 index 0000000..4885f8f --- /dev/null +++ b/app/models/event.rb @@ -0,0 +1,94 @@ +# 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 Event < ActiveRecord::Base + belongs_to :ev_type + belongs_to :participant + belongs_to :message + + def ===(y) + participant_id == y.participant_id and message_id == y.message_id and ev_type_id == y.ev_type_id + end + +private + + # if count <0 then list all events otherwise maximal count events + named_scope :for_participant, lambda { |participant_id,count| { + :conditions => { :participant_id => participant_id }, + :order => "id ASC", + count<0 ? :readonly : :limit => count }} + named_scope :for_participant_and_message_desc_order, lambda { |participant_id,message_id| { + :conditions => { :participant_id => participant_id, :message_id => message_id }, + :order => "id DESC", + :limit => 1 }} + + def self.make(options) + options.assert_valid_keys(:event_type_name, :membership_message, :participant, :message) + message = options[:membership_message] ? options[:membership_message].message : options[:message] + participant= options[:membership_message] ? options[:membership_message].membership.participant : options[:participant] + return if not (message.ressource.events? and participant.events?) + event = Event.new + event.participant_id = participant.id + event.message_id = message.id + case options[:event_type_name] + when "created" + event.ev_type_id = EvType.find_by_name("created").id + when "destroyed" + event.ev_type_id = EvType.find_by_name("destroyed").id + when "updated" + event.ev_type_id = EvType.find_by_name("updated").id + else event.ev_type_id = 7777 + end + if unique_or_notlast?(event) and event.save + event + else + # There is already a pending event (the last one) describing a change of + # the message. So don't create another one. Instead only touch the + # "updated_at" attribute of the event. + iev= Event.for_participant_and_message_desc_order(event.participant_id, event.message_id)[0] + iev.updated_at= Time.now.to_s(:db) + iev.save + nil + end + end + + def self.unique_or_notlast?(event) + # Normally there should/could only be multiple update events more than + # once. The testing code would also handle all other events correctly. + mid= event.message_id + pid= event.participant_id + etid= event.ev_type_id + case initial_event(pid, mid, etid) + when nil + # its a unique event + return true + when Event.for_participant_and_message_desc_order(pid, mid)[0] + # there is already such an event for that message and its the last in the queue + # for case equality see also overridden === case operator + return false + else + # there is such an event but it's not the last in the queue so create a new one + return true + end + end + + def self.initial_event(pid, mid, etid) + Event.find_by_participant_id_and_message_id_and_ev_type_id(pid, mid, etid) + end + +end diff --git a/app/models/identity.rb b/app/models/identity.rb new file mode 100644 index 0000000..f6f3cb7 --- /dev/null +++ b/app/models/identity.rb @@ -0,0 +1,36 @@ +# 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 Identity < ActiveRecord::Base + + require 'securerandom' + + belongs_to :participant + + # TODO validate :participant_id + # it's possible in rails 2.3.6 with :inverse_of + validates_presence_of :name + validates_uniqueness_of :name + + + def self.randomized_authid + SecureRandom.hex + end + +end + diff --git a/app/models/logging_observer.rb b/app/models/logging_observer.rb new file mode 100644 index 0000000..d920666 --- /dev/null +++ b/app/models/logging_observer.rb @@ -0,0 +1,15 @@ +class LoggingObserver < ActiveRecord::Observer + observe Event + + def after_create(model) + case model + when Event + msgpath= "#{model.message.ressource.namespace}/#{model.message.ressource.ressource}/#{model.message.id}" + evreceiver_pid= model.participant.id + evreceiver_mid= (Membership.receiver(evreceiver_pid, model.message.id)).id + evtype= model.ev_type.name + model.logger.info("**#{model.message.ressource.namespace}** Event: Type:#{evtype} -- MsgPath:#{msgpath} -- ReceiverPid:#{evreceiver_pid} -- ReceiverMid:#{evreceiver_mid}") + end + end + +end diff --git a/app/models/membership.rb b/app/models/membership.rb new file mode 100644 index 0000000..fda3093 --- /dev/null +++ b/app/models/membership.rb @@ -0,0 +1,120 @@ +# 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 Membership < ActiveRecord::Base + belongs_to :participant + belongs_to :community + belongs_to :community_with_reduced_attributes, + :class_name => "Community", + :foreign_key => "community_id", + :select => "name, description, id" + has_many :messages, :through => :membership_messages + has_many :membership_messages, :dependent => :destroy + + after_create :postroute + + # returns memberships of the relation between a participant and a message + # if no relationship then returns empty array. + named_scope :receiver, lambda { |participant_id,message_id| { + :joins => [:participant, {:membership_messages => :message}], + :conditions => { :participants => { :id => participant_id }, :messages => { :id => message_id } } } } + + named_scope :receivers, lambda { |message_id| { + :joins => [:membership_messages => :message], + :select => :memberships.to_s+".id" + ", community_id, participant_id", + :conditions => { :messages => { :id => message_id } } } } + + named_scope :for_participant_id, lambda { |participant_id| { + :joins => [:participant], + :conditions => { :participants => { :id => participant_id } } } } + + named_scope :for_participant_id_and_community_id, lambda { |participant_id,community_id| { + :joins => [:participant, :community], + :conditions => { :participants => { :id => participant_id }, :communities => { :id => community_id } } } } + + def self.senders(participant, message) + sender_mids=[] + Community.for_participant(participant).for_message(message).uniq.each do |comm| + sender_mids << Membership.find_by_participant_id_and_community_id(participant.id,comm.id) + end + if sender_mids.empty? + [] + else + sender_mids.flatten + end + end + + def self.memberships(participant,itsyou=false,filter=nil) + memberships = [] + Membership.for_participant_id(participant.id).each do |membership| + community= lambda { |memb| + attribs = memb.community_with_reduced_attributes.attributes + id = attribs["id"]; attribs.delete("id"); attribs["cid"] = id + attribs + }.call(membership) + logger.debug "**** Membership::memberships: community: #{community.inspect}" + if itsyou + participants_with_reduced_attribs= membership.community.participants.itsyou(participant.id).without_anonymous.reduced_attributes + logger.debug "**** Membership::memberships: participants_with_reduced_attribs: #{participants_with_reduced_attribs.inspect}" + else + participants_with_reduced_attribs= case + when filter[:all] + membership.community.participants.order_id_asc.reduced_attributes + when filter[:mainparticipants] + membership.community.participants.mainparticipants_with_reduced_attributes + when filter[:subparticipants] + membership.community.participants.subparticipants_with_reduced_attributes + when filter[:anonymous] + membership.community.participants.anonymous_participants_with_reduced_attributes + else + membership.community.participants.mainparticipants_with_reduced_attributes + end + end + participants= participants_with_reduced_attribs.map do |p| + attribs = p.attributes + attribs["mid"] = Membership.for_participant_id_and_community_id(p.id, membership.community.id).first.id + attribs["org"] = {"name" => p.organization.name, "abbr" => p.organization.abrev} + attribs["itsyou"] = p.id == participant.id + attribs["pid"] = p.id + attribs["type"] = p.ptype + attribs.delete("id") + attribs.delete("organization_id") + attribs.delete("ptype") + attribs + end + logger.debug "**** Membership::memberships: participants: #{participants.inspect}" + memberships << + { :community => community, + :participants => participants + } + end + memberships + end + +private + + # generate created events for all messages connected to this community membership + def postroute + community.messages.map{|m| m.ressource.postroute ? m : nil}.compact.each do |msg| + messages << msg + Event.make(:event_type_name => EvType.find_by_name("created").name, :participant => participant, :message => msg) + logger.info "**** postrouting message.id=#{msg.id} to participant:#{participant.name} (pid:#{participant.id})" + end + end + +end diff --git a/app/models/membership_message.rb b/app/models/membership_message.rb new file mode 100644 index 0000000..e196902 --- /dev/null +++ b/app/models/membership_message.rb @@ -0,0 +1,126 @@ +# 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 MembershipMessage < ActiveRecord::Base + belongs_to :membership + belongs_to :message + + # Populate the memberships_messages jointable if sender joins community with receiver + def self.populate_jointable(record, x_ecs_receiver_memberships, x_ecs_receiver_communities, sender_participant) + rec_mids = extract_x_ecs_receiver_memberships(x_ecs_receiver_memberships) + rec_cids = extract_x_ecs_receiver_communities(x_ecs_receiver_communities) + if rec_cids.blank? and rec_mids.blank? + raise Ecs::MissingReceiverHeaderException, + "You must at least specify one of X-EcsReceiverMemberships or X-EcsReceiverCommunities header\r\n" + end + + pop_succ = false + memberships_for_sender_participant_id = Membership.for_participant_id(sender_participant.id) + + rec_mids.each do |rmid| + # Test if sender joins same community as receiver + if memberships_for_sender_participant_id.map{|m| m.community.id}.include?(Membership.find(rmid).community.id) + Membership.find(rmid).messages << record unless MembershipMessage.find_by_membership_id_and_message_id(rmid, record.id) + pop_succ = true + end + end + + rec_cids.each do |rcid| + # Test if sender joins same community as receiver + if memberships_for_sender_participant_id.map{|m| m.community.id}.include?(rcid) + Community.find(rcid).memberships.each do |membership| + if !MembershipMessage.find_by_membership_id_and_message_id(membership.id, record.id) and # relation already made + (sender_participant.community_selfrouting or # address sender through community + membership.participant.id != sender_participant.id) # address sender through community + membership.messages << record + end + end + pop_succ = true + end + end + + unless pop_succ + raise Ecs::AuthorizationException, + "You are not joining at least one of the community to which you are addressing.\r\n" + + "or\r\n" + + "You are not joining at least one of the same community as the receiving membership.\r\n" + end + rescue ActiveRecord::RecordNotFound + raise Ecs::InvalidMessageException, + "Membership id in X-EcsReceiverMemberships header not found." + end + + # Depopulate the memberships_messages jointable + def self.de_populate_jointable(record) + record.membership_messages.each do |mm| + mm.destroy + end + end + + def self.extract_x_ecs_receiver_communities(erc) + receiver_communities= [] + erc.split(',').map {|e| e.strip}.each do |comm_str| + if comm_str =~ /\d{#{comm_str.length}}/ + # comm_str has only digits + receiver_communities << comm_str.to_i + else + # comm_str should be a community name + comm= Community.find_by_name(comm_str) + if comm == nil then + raise Ecs::InvalidMessageException, "community id/name in X-EcsReceiverCommunities header not found: #{comm_str}" + end + receiver_communities << comm.id + end + end unless erc.blank? + receiver_communities.uniq! + receiver_communities + end + + def self.extract_x_ecs_receiver_memberships(erm) + receiver_memberships= [] + erm.split(',').map {|e| e.strip}.each do |memb_str| + if memb_str =~ /\d{#{memb_str.length}}/ + # memb_str has only digits + #receiver_memberships.concat Membership.find(memb_str.to_i).community.memberships.map{ |m| m.id } + receiver_memberships << memb_str.to_i if Membership.find(memb_str.to_i) + else + # memb_str is invalid, because it's not an integer value + # raise Exception + end + end unless erm.blank? + receiver_memberships.uniq! + receiver_memberships + end + + +private + + # Deletes all records with relations between a record and the given + # memberships or all record with relation to the given message + # (memberships=nil) + def self.delete_relations(message, memberships=nil) + if memberships + memberships.each do |m| + destroy_all ["membership_id = ? and message_id = ?", m.id, message.id] + end + else + destroy_all ["message_id = ?", message.id] + end + end + +end diff --git a/app/models/message.rb b/app/models/message.rb new file mode 100644 index 0000000..24eb296 --- /dev/null +++ b/app/models/message.rb @@ -0,0 +1,324 @@ +# 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}}, + :readonly => false}} + + named_scope :for_participant_sender, lambda {|participant| { + :order => "id ASC", + :conditions => {:sender => participant.id}, + :readonly => false}} + + 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}) + m=find(:all, :readonly => false, :lock => true, + :select => "messages.id", + :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'}") + if m.empty? then nil else find(m[0]) end + 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 diff --git a/app/models/organization.rb b/app/models/organization.rb new file mode 100644 index 0000000..2fcbb3c --- /dev/null +++ b/app/models/organization.rb @@ -0,0 +1,32 @@ +# 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 Organization < ActiveRecord::Base + # TODO: warn the user about possible deletions of participants. + has_many :participants, :dependent => :destroy + + validates_presence_of :name, :abrev + validates_uniqueness_of :name, :abrev + + def orgtext + "#{name} (#{abrev})" + end + def orgtext=(name) + return + end +end diff --git a/app/models/participant.rb b/app/models/participant.rb new file mode 100644 index 0000000..5fc363c --- /dev/null +++ b/app/models/participant.rb @@ -0,0 +1,169 @@ +# Copyright (C) 2007, 2008, 2009, 2010, 2014 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 Participant < ActiveRecord::Base + TTL = 1.hour # how long an anonymous participant lives + TYPE={ :main => "main", :sub => "sub", :anonym => "anonym" } + + after_destroy :delete_messages + + belongs_to :organization + # TODO: warn user about possible deletions of messages. + has_many :memberships, :dependent => :destroy + has_many :communities, :through => :memberships + has_many :identities, :dependent => :destroy + has_many :events, :dependent => :destroy + has_many :childs, + :order => "id ASC", + :class_name => "Subparticipant", + :foreign_key => "parent_id", + :dependent => :destroy + has_one :subparticipant, :dependent => :destroy + + validates_presence_of :name, :organization_id + validates_uniqueness_of :name + + accepts_nested_attributes_for :identities, :allow_destroy => true, :reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } } + accepts_nested_attributes_for :communities, :reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } } + accepts_nested_attributes_for :subparticipant, :allow_destroy => true + + named_scope :order_id_asc, :order => "participants.id ASC" + named_scope :without_anonymous, :conditions => { :participants => { :anonymous => false } } + named_scope :anonymous, :conditions => { :participants => { :anonymous => true } } + named_scope :for_message, lambda { |message| { + :joins => [:memberships => {:membership_messages => :message}], + :conditions => {:messages => {:id => message.id}}}} + named_scope :for_community, lambda { |community| { + :joins => [:memberships => :community], + :conditions => { :communities => { :id => community.id }}}} + named_scope :for_subparticipants + named_scope :itsyou, lambda { |itsyoupid| { :conditions => { :participants => { :id => itsyoupid } } } } + named_scope :only_subparticipants, :conditions => { :participants => { :ptype => TYPE[:sub] } } + named_scope :only_mainparticipants, :conditions => { :participants => { :ptype => TYPE[:main] } } + named_scope :only_anonymous, :conditions => { :participants => { :ptype => TYPE[:anonym] } } + + def self.reduced_attributes + find :all, :select => "participants.id, participants.name, participants.description, participants.email, participants.dns, participants.organization_id, participants.ptype" + end + + def self.mainparticipants_with_reduced_attributes + only_mainparticipants.order_id_asc.reduced_attributes + end + + def self.subparticipants_with_reduced_attributes + only_subparticipants.order_id_asc.reduced_attributes + end + + def self.anonymous_participants_with_reduced_attributes + only_anonymous.order_id_asc.reduced_attributes + end + + def destroy_receiver_messages + Message.for_participant_receiver(self).each do |m| + m.destroy_as_receiver(self) + end + end + + def destroy_sender_messages + Message.for_participant_sender(self).each do |m| + m.destroy_as_sender + end + end + + def destroy_events + self.events.each do |e| + e.destroy + end + end + + def mainparticipant? + if not anonymousparticipant? and subparticipant.nil? + true + else + false + end + end + + def subparticipant? + if not anonymousparticipant? and not subparticipant.nil? + true + else + false + end + end + + def anonymousparticipant? + anonymous? + end + + # test if the participant is the initial sender of the message in question. + def sender?(message) + if message.sender == id + true + else + false + end + end + + def receiver?(message) + not Membership.receiver(id, message.id).empty? + end + + def events? + self.events_.blank? ? false : true + end + + def anonymous? + anonymous + end + + def self.generate_anonymous_participant + cookie = Digest::SHA1.hexdigest('something secret'+Time.now.to_s+rand.to_s) + params = { + "name"=> "#{cookie}", + "identities_attributes"=>{"0"=>{"name"=>"#{cookie}", "description"=>"Cookie Identifier"}}, + "community_ids"=>[Community.find_by_name("public").id], + "description"=>"Anonymous Participant", + "dns"=>"N/A", + "organization_id"=>Organization.find_by_name("not available").id, + "email"=>"N/A", + "ttl"=> DateTime.now.utc + TTL, + "anonymous"=>true, + "ptype"=>TYPE[:anonym] + } + ap = new(params) + ap.save! + return ap, cookie + end + + def self.touch_ttl(participant) + participant.ttl = DateTime.now.utc + TTL + participant.save + end + + def mid(community) + Membership.for_participant_id_and_community_id(self, community.id).first.id + end + +private + + def delete_messages + Message.destroy_all(["sender = ?", self.id]) + end + +end diff --git a/app/models/ressource.rb b/app/models/ressource.rb new file mode 100644 index 0000000..0a741dd --- /dev/null +++ b/app/models/ressource.rb @@ -0,0 +1,50 @@ +# 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 Ressource < ActiveRecord::Base + has_many :messages, :dependent => :destroy + validates_presence_of :namespace, :ressource + after_save :rebuild_routes + after_destroy :rebuild_routes + + named_scope :list, :order => "namespace, ressource ASC" + + def self.validates_ressource_path(namespace, ressource) + r = Ressource.find_by_namespace_and_ressource(namespace, ressource) + raise(Ecs::InvalidRessourceUriException, "*** ressource uri error ***") unless r + if namespace.blank? or r.namespace.blank? + raise Ecs::InvalidRessourceUriException, "*** namespace error ***" + end + if ressource.blank? or r.ressource.blank? + raise Ecs::InvalidRessourceUriException, "*** ressource name error ***" + end + return r + end + + def events? + self.events.blank? ? false : true + end + +private + + def rebuild_routes + logger.info("rebuild routes") + ActionController::Routing::Routes.reload! + end + +end diff --git a/app/models/subparticipant.rb b/app/models/subparticipant.rb new file mode 100644 index 0000000..2efc504 --- /dev/null +++ b/app/models/subparticipant.rb @@ -0,0 +1,137 @@ +# Copyright (C) 2014 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 Subparticipant < ActiveRecord::Base + + TTL = 3600 # seconds, how long a subparticipant lives, after last + # communication with ECS + + require 'securerandom' + + belongs_to :parent, + :class_name => "Participant", + :foreign_key => "parent_id" + + belongs_to :participant + + + def self.generate(parent, json_data) + auth_id= Identity.randomized_authid + data = process_json_data(parent, json_data) + check_valid_communities(parent ,data[:community_ids]) + params = { + "name" => "Subparticipant (\##{SecureRandom.hex}) from #{parent.name}", + "identities_attributes" => {"0"=>{"name"=>"#{auth_id}", "description"=>"Randomized authid"}}, + "community_ids" => data[:community_ids], + "description" => "", + "dns" => "N/A", + "organization_id" => parent.organization.id, + "email" => parent.email, + "ttl" => nil, + "anonymous" => false, + "ptype" => Participant::TYPE[:sub], + "community_selfrouting" => data[:community_selfrouting], + "events_" => data[:events], + "subparticipant_attributes" => { :realm => data[:realm] } + } + participant = Participant.new(params) + participant.save! + subp= participant.subparticipant + subp.parent= parent + subp.save! + participant.name= "Subparticipant (id:#{subp.id})" + participant.description= "Created from \"#{parent.name}\" (pid:#{parent.id})" + participant.save! + subp + end + + def update__(parent, json_data, subparticipant) + participant= subparticipant.participant + auth_id= "dummy" + data= process_json_data(parent, json_data) + Subparticipant::check_valid_communities(parent, data[:community_ids]) + params = { + "community_selfrouting" => data[:community_selfrouting], + "community_ids" => data[:community_ids], + "events_" => data[:events], + "subparticipant_attributes" => { :id => self.id.to_s, :realm => data[:realm] } + } + participant.update_attributes(params) + end + +private + + def process_json_data(sender, json_data) + Subparticipant::process_json_data(sender, json_data) + end + + def self.process_json_data(sender, json_data) + json_data= {} unless json_data.class == Hash + realm= json_data["realm"] ||= nil + community_selfrouting= json_data["community_selfrouting"] || false + events= json_data["events"] ||= true + if json_data["communities"] + community_ids= json_data["communities"].map do |comm| + case + when comm.class == Fixnum + if Community.find_by_id(comm) + comm + else + raise Ecs::InvalidMessageException, comm.to_s + end + when comm.class == String + if (c= Community.find_by_name(comm)) + c.id + else + raise Ecs::InvalidMessageException, comm.to_s + end + else + nil + end + end + end + community_ids ||= [] + community_ids.compact! + { :realm => realm, :community_selfrouting => community_selfrouting, :events => events, + :community_ids => community_ids } + rescue Ecs::InvalidMessageException + errortext= <<-END +You provided at least one unknown community for a subparticipant creation. +Following community is unknown (either a cid or a community name): #{$!} + END + raise Ecs::InvalidMessageException, errortext + end + + def self.check_valid_communities(parent, community_ids) + if community_ids.blank? + logger.debug "Subparticipant#check_valid_communities: empty community_ids" + return + end + parent_community_ids= parent.communities.map{|c| c.id} + logger.debug "Subparticipant#check_valid_communities: parent community ids = [#{parent_community_ids.join(', ')}]" + logger.debug "Subparticipant#check_valid_communities: subparticipant community ids = [#{community_ids.join(', ')}]" + logger.debug "Subparticipant#check_valid_communities: Difference between subparticipant community ids and parent community ids = [#{(community_ids - parent_community_ids).join(', ')}]" + unless (community_ids - parent_community_ids).blank? + errortext= <<-END +The subparticipant's communities must be a subset of its parent. +Following communities are not allowed: #{(community_ids - parent_community_ids).map{|cid|Community.find(cid).name}.join(', ')} + END + raise Ecs::InvalidMessageException, errortext + end + end + +end |