root/trunk/lib/secure.rb

Revision 1176, 31.6 kB (checked in by gaspard, 4 months ago)

commit a4d2b8b06f67d679e62f7c69865ed774ac166fc3
Author: Gaspard Bucher <gaspard@teti.ch>

Fixed a bug where user's with a low status (less then 'user' = reader, commentator, ...) could still write/publish content.

Line 
1 module Zena
2   # version status 
3   Status = {
4     :pub  => 50,
5     :prop => 40,
6     :prop_with => 35,
7     :red_visible => 33,
8     :red  => 30,
9     :rep  => 20,
10     :rem  => 10,
11     :del  => 0,
12   }.freeze
13  
14   module Acts
15 =begin rdoc
16 == Secure model
17 Read, write and publication access to an node is defined with four elements: one user and three groups.
18 link://rwp_groups.png
19
20 === Definitions :
21 [inherit]  Defines how the groups propagate. If +inherit+ is set to '1', the node inherits rwp groups from it's reference. If
22            +inherit+ is set to '0', the node has custom rwp groups. When set to '-1', the node is becomes private and all
23            rwp groups are set to '0'.
24 [read]
25     This means that the node can be seen.
26 [write]
27     This means that new versions can be proposed for the node as well as new
28     sub-pages, documents, events, etc. Basically can write = can add content. If a user has write access to
29     a #Tag, this means he can add nodes to this #Tag (#Tag available as a category for other nodes).
30 [publish]
31     This means that the content viewed by all can be altered by
32     1. publishing new versions
33     2. changing the node itself (name, groups, location, categories, etc)
34     3. removing the node and/or sub-nodes
35     4. people with this access can see nodes that are not published yet
36 [manage]
37     This is for nodes that <em>have not yet been published</em> or for <em>private nodes</em>
38     A. <em>private node</em>
39     1. can 'publish' node (it is not really published as the node is private...)
40     2. can 'unpublish' (make this node a 'not published yet')
41     3. can change node itself (cannot change groups)
42     4. can destroy
43     B. <em>node not published yet</em> only :
44     5. make an node private (sets all groups to 0) or revert node to default groups (same as parent or project) if node not published yet
45     5. can see node (edition = personal redaction or latest version)
46 [max_status]
47     This is set to the highest status of all versions. Order from highest to lowest are : 'pub', 'prop', 'red', 'rep', 'rem', 'del'
48
49 === Who can do what
50 [read]
51 * super user
52 * owner
53 * members of +read_group+ if the node is published and the current date is greater or equal to the publication date
54 * members of +publish_group+ if +max_status+ >= prop
55  
56 [write]
57 * super user
58 * owner
59 * members of +write_group+ if node is published and the current date is greater or equal to the publication date
60  
61 [publish]
62 * super user
63 * members of +publish_group+ if +max_status+ >= prop
64 * owner if member of +publish_group+
65
66 [manage]
67 * owner if +max_status+ <= red
68 * owner if private
69
70 === Misc
71
72 * A user can only set a group in which he/she belongs.
73 * Only people from the 'admin' group can change an node's owner.
74 * Setting all groups to _public_ transforms the node into a wiki.
75 * A user who belongs to the 'admin' group (id=2), automatically belongs to all other groups.
76
77 === Usage
78
79 In the controller :
80   require 'lib/acts_as_secure'
81   class PagesController < ApplicationController
82     before_filter :set_logged_in_user
83     acts_as_secure
84
85     def show
86       @page = secure { Page.find(params[:id]) }
87     end
88     private
89     def set_logged_in_user
90       # .. get user
91       session[:user] = @user[:id]
92   end
93
94 #FIXME: correct doc.
95 In the model :
96   require 'lib/acts_as_secure'
97   class Page < ActiveRecord::Base
98     acts_as_secure_node
99   end
100
101 In the helpers (if you intend to use secure find there...)
102   require 'lib/acts_as_secure'
103   module ApplicationHelper
104     include Zena::Acts::Secure
105     # ...
106   end
107 Just doing the above will filter all result according to the logged in user.
108 =end
109     module SecureNode
110       # this is called when the module is included into the 'base' module
111       def self.included(base)
112         # add all methods from the module "AddActsAsMethod" to the 'base' module
113         base.extend AddActsAsMethod
114       end
115       module AddActsAsMethod
116         def acts_as_secure_node
117           belongs_to :rgroup, :class_name=>'Group', :foreign_key=>'rgroup_id'
118           belongs_to :wgroup, :class_name=>'Group', :foreign_key=>'wgroup_id'
119           belongs_to :pgroup, :class_name=>'Group', :foreign_key=>'pgroup_id'
120           belongs_to :user
121           before_validation :secure_before_validation
122           after_save :check_inheritance
123           before_destroy :secure_on_destroy
124           class_eval <<-END
125             include Zena::Acts::SecureNode::InstanceMethods
126           END
127         end
128       end
129      
130      
131       module InstanceMethods
132        
133         def self.included(base)
134           base.extend ClassMethods
135         end
136          
137         # Store visitor to produce scope when needed and to retrieve correct editions.
138         def visitor=(visitor)
139           @visitor = visitor
140           if new_record?
141             set_on_create
142           end
143           # callback used by functions triggered before 'visitor='
144           if @eval_on_visitor
145             @eval_on_visitor.each do |str|
146               eval(str)
147             end
148             unless errors.empty?
149               raise ActiveRecord::RecordNotFound
150             end
151           end
152           self
153         end
154        
155         # list of callbacks to trigger when set_visitor is called
156         def eval_with_visitor(str)
157           @eval_on_visitor ||= []
158           @eval_on_visitor << str
159           self
160         end
161    
162         # Return true if the node is considered as private (+read_group+, +write_group+ and +publish_group+ are +0+)
163         def private?
164           (rgroup_id==0 && wgroup_id==0 && pgroup_id==0)
165         end
166        
167         # Return true if the node can be viewed by all (public)
168         def public?
169           can_read?(visitor.site.anon,visitor.site.anon.group_ids) # visible by anonymous
170         end
171  
172         # people who can read:
173         # * super user
174         # * owner
175         # * members of +read_group+ if the node is published and the current date is greater or equal to the publication date
176         # * members of +publish_group+ if +max_status+ >= prop
177         def can_read?(vis = visitor, ugps=visitor.group_ids)
178           ( vis.is_su? ) || # super user
179           ( vis[:id] == user_id ) ||
180           ( ugps.include?(rgroup_id) && publish_from && Time.now >= publish_from ) ||
181           ( ugps.include?(pgroup_id) && max_status != Zena::Status[:red] )
182         end
183  
184         # people who can write:
185         # * super user
186         # * owner if visitor's status is at least 'user'
187         # * members of +write_group+ if published and the current date is greater or equal to the publication date and the visitor's status is at least 'user'
188         def can_write?(vis=visitor, ugps=visitor.group_ids)
189           ( vis.is_su? ) || # super user
190           ( vis.user? && (( vis[:id] == user_id ) ||
191           ( ugps.include?(wgroup_id) && max_status != Zena::Status[:red] )))
192         end
193        
194         # people who can make visible changes
195         # * super user
196         # * members of +publish_group+ if member status is at least 'user'
197         # * visitor status is at least 'user' and is a member of the reference's publish group if the item is private
198         def can_visible?(vis=visitor, ugps=visitor.group_ids)
199           ( vis.is_su? ) || # super user
200           ( vis.user? && (( ugps.include?(pgroup_id) ) ||
201           ( private? && ugps.include?(ref.pgroup_id))))
202         end
203  
204         # people who can manage:
205         # * owner if visitor's status is at least 'user' and node's +max_status+ <= red
206         # * owner if visitor's status is at least 'user' and node is private
207         def can_manage?(vis=visitor)
208           ( vis.is_su? ) || # super user
209           ( vis.user? && (( publish_from == nil && vis[:id] == user_id && max_status <= Zena::Status[:red] ) ||
210           ( private? && vis[:id] == user_id )))
211         end
212        
213         # can change position, name, rwp groups, etc
214         def can_drive?
215           can_manage? || can_visible?
216         end
217        
218         def secure_before_validation
219           unless @visitor
220             errors.add('base', "record not secured")
221             return false
222           end
223           self[:site_id] = @visitor.site[:id]
224           if new_record?
225             set_on_create
226             secure_on_create
227           else
228             secure_on_update
229           end
230         end
231        
232         # Set owner and lang before validations on create (overwritten by multiversion)
233         def set_on_create
234           # set kpath
235           self[:kpath]    = self.class.kpath
236           self[:user_id]  = visitor[:id]
237           self[:ref_lang] = visitor.lang
238         end
239        
240         # 1. validate the presence of a valid project (one in which the visitor has write access and project<>self !)
241         # 2. validate the presence of a valid reference (project or parent) (in which the visitor has write access and ref<>self !)
242         # 3. validate +publish_group+ value (same as parent or ref.can_visible? and valid)
243         # 4. validate +rw groups+ :
244         #     a. if can_visible? : valid groups
245         #     b. else inherit or private
246         # 5. validate the rest
247         def secure_on_create
248           # validate reference
249           if ref == nil
250             errors.add(ref_field, "invalid reference")
251             return false
252           end
253           [:rgroup_id, :wgroup_id, :pgroup_id, :skin].each do |sym|
254             # not defined = inherit
255             self[sym] ||= ref[sym]
256             self[sym] = 0 if self[sym] == ''
257           end
258           if inherit.nil?
259             if rgroup_id == ref.rgroup_id && wgroup_id == ref.wgroup_id && pgroup_id == ref.pgroup_id
260               self[:inherit  ] = 1
261             else
262               self[:inherit  ] = 0
263             end
264           end
265           case inherit
266           when 1
267             # force inheritance
268             self[:rgroup_id] = ref.rgroup_id
269             self[:wgroup_id] = ref.wgroup_id
270             self[:pgroup_id] = ref.pgroup_id
271             self[:skin ] = ref.skin
272           when -1
273             # private
274             if visitor.site[:allow_private]
275               self[:rgroup_id] = 0  # FIXME: why not just use nil ? (NULL in db)
276               self[:wgroup_id] = 0  # FIXME: why not just use nil ? (NULL in db)
277               self[:pgroup_id] = 0  # FIXME: why not just use nil ? (NULL in db)
278             else
279               errors.add('inherit', "you cannot change this")
280             end
281           when 0
282             if ref.can_visible?
283               errors.add('rgroup_id', "unknown group") unless visitor.group_ids.include?(rgroup_id)
284               errors.add('wgroup_id', "unknown group") unless visitor.group_ids.include?(wgroup_id)
285               errors.add('pgroup_id', "unknown group") unless visitor.group_ids.include?(pgroup_id)
286             elsif private?
287               # ok
288             else
289               errors.add('inherit', "invalid value")
290               errors.add('rgroup_id', "you cannot change this") unless rgroup_id == ref.rgroup_id
291               errors.add('wgroup_id', "you cannot change this") unless wgroup_id == ref.wgroup_id
292               errors.add('pgroup_id', "you cannot change this") unless pgroup_id == ref.pgroup_id
293               errors.add('skin' , "you cannot change this") unless skin  == ref.skin
294             end
295           else
296             errors.add('inherit', "bad inheritance mode")
297           end
298
299           # publish_from can only be set by the object itself by setting @publish_from
300           self[:publish_from] = version.publish_from
301           # same for proposed
302           self[:max_status] = version.status
303           return errors.empty?
304         end
305
306         # 1. if pgroup changed from old, make sure user could do this and new group is valid
307         # 2. if owner changed from old, make sure only a user in 'admin' can do this
308         # 3. error if user cannot publish nor manage
309         # 4. parent/project changed ? verify 'publish access to new *and* old'
310         # 5. validate +rw groups+ :
311         #     a. can change to 'inherit' if can_visible? or can_manage? and max_status < pub and does not have children
312         #     b. can change to 'private' if can_manage?
313         #     c. can change to 'custom' if can_visible?
314         # 6. validate the rest
315         def secure_on_update
316           @old = nil # force reload of 'old'
317           unless old
318             # cannot change node if old not found
319             errors.add('base', "you do not have the rights to do this")
320             return false
321           end
322           if !( old.can_drive? )
323             errors.add('base', "you do not have the rights to do this")
324             return false
325           end
326           if user_id != old.user_id
327             if visitor.is_admin?
328               # only admin can change owners
329               unless User.find(:first, :conditions => ["id = ?",user_id])
330                 errors.add('user_id', "unknown user")
331               end
332             else
333               errors.add('user_id', "you cannot change this")
334             end
335           end
336           return false unless errors.empty?
337           # verify reference
338           if ref == nil
339             errors.add(ref_field, "invalid reference")
340             return false
341           end
342           if self[ref_field] != old[ref_field]
343             # reference changed
344             if old.private? || old.publish_from == nil
345               # node was not visible to others
346               if self[ref_field] == self[:id] ||
347                   ! secure_write(ref_class) { ref_class.find(self[ref_field])} ||
348                   ! secure_write(ref_class) { ref_class.find(old[ref_field])}
349                 errors.add(ref_field, "invalid reference")
350                 return false
351               end
352             else
353               # node was visible, moves must be made with publish rights in both
354               # source and destination
355               if self[ref_field] == self[:id] ||
356                   ! secure_drive(ref_class) { ref_class.find(self[ref_field]) } ||
357                   ! secure_drive(ref_class) { ref_class.find(old[ref_field])  }
358                 errors.add(ref_field, "invalid reference")
359                 return false
360               end
361             end
362             # check circular references
363             ref_ids  = [self[:id]]
364             curr_ref = self[:parent_id]
365             ok = true
366             while curr_ref != 0
367               if ref_ids.include?(curr_ref) # detect loops
368                 ok = false
369                 break
370               end
371               ref_ids << curr_ref
372               rows = self.class.connection.execute("SELECT #{ref_field} FROM #{self.class.table_name} WHERE id=#{curr_ref}")
373               if rows.num_rows == 0
374                 errors.add(ref_field, "reference missing in reference hierarchy")
375                 raise ActiveRecord::RecordNotFound
376               end
377               curr_ref = rows.fetch_row[0].to_i
378             end
379             unless ok
380               errors.add(ref_field, 'circular reference')
381               return false
382             end
383           end
384           # publish_from can only be set by the object itself by setting @publish_from
385           self[:publish_from] = @publish_from || old.publish_from
386           # same with proposed
387           self[:max_status] = @max_status || old.max_status
388           # verify groups
389           [:rgroup_id, :wgroup_id, :pgroup_id].each do |sym|
390             # set to 0 if nil or ''
391             self[sym] = 0 if !self[sym] || self[sym] == ''
392           end
393           if self[:inherit] == 0 && pgroup_id == 0
394             # if pgroup_id is set to 0 ==> make node private
395             self[:inherit] = -1
396           end 
397           case inherit
398           when 1
399             # inherit
400             if inherit != old.inherit && !(old.can_visible? || ( old.can_manage? && (old.max_status_with_heirs < Zena::Status[:pub]) ))
401               errors.add('inherit', 'you cannot change this')
402               return false
403             end
404            
405             # make sure rights are inherited.
406             [:rgroup_id, :wgroup_id, :pgroup_id, :skin].each do |sym|
407               self[sym] = ref[sym] 
408             end
409           when -1
410             # make private, only if owner
411             unless (inherit == old.inherit)
412               if old.can_drive? && (user_id == visitor[:id]) && visitor.site[:allow_private]
413                 [:rgroup_id, :wgroup_id, :pgroup_id].each do |sym|
414                   self[sym] = 0
415                 end
416               else
417                 errors.add('inherit', "you cannot change this")
418               end
419             end
420           when 0
421             if old.can_visible?
422               if ref.can_visible?
423                 # can change groups
424                 if private?
425                   # ok (all groups are 0)
426                 else
427                   [:rgroup_id, :wgroup_id, :pgroup_id].each do |sym|
428                     if self[sym] != 0 && self[sym] != old[:rgroup_id] && !visitor.group_ids.include?(self[sym])
429                       errors.add(sym.to_s, "unknown group")
430                     end
431                   end
432                 end
433               else
434                 # cannot change groups or inherit mode
435                 errors.add('inherit', "you cannot change this") unless inherit == old.inherit
436                 errors.add('rgroup_id', "you cannot change this") unless rgroup_id == old.rgroup_id
437                 errors.add('wgroup_id', "you cannot change this") unless wgroup_id == old.wgroup_id
438                 errors.add('pgroup_id', "you cannot change this") unless pgroup_id == old.pgroup_id
439                 # but you can change skins and name
440               end
441             else
442               # must be the same as old
443               errors.add('inherit', "you cannot change this") unless inherit == old.inherit
444               errors.add('rgroup_id', "you cannot change this") unless rgroup_id == old.rgroup_id
445               errors.add('wgroup_id', "you cannot change this") unless wgroup_id == old.wgroup_id
446               errors.add('pgroup_id', "you cannot change this") unless pgroup_id == old.pgroup_id
447               errors.add('skin', "you cannot change this") unless  skin  == old.skin
448             end
449           else
450             errors.add('inherit', "bad inheritance mode")
451           end
452           @needs_inheritance_spread = (rgroup_id != old.rgroup_id || wgroup_id != old.wgroup_id || pgroup_id != old.pgroup_id || skin != old.skin)
453           return errors.empty?
454         end
455        
456         def secure_on_destroy
457           if old && old.can_drive?
458             return true
459           else
460             errors.add('base', "you do not have the rights to do this")
461             return false
462           end         
463         end
464
465         # Reference to validate access rights
466         def ref
467           return self if ref_field == :id && new_record? # new record and self as reference (creating root node)
468           if !@ref || (@ref.id != self[ref_field])
469             # no ref or ref changed
470             @ref = secure(ref_class) { ref_class.find(:first, :conditions => ["id = ?", self[ref_field]]) }
471           end
472           if @ref && (self.new_record? || (:id == ref_field) || (self[:id] != @ref[:id] ))
473             # reference is accepted only if it is not the same as self or self is root (ref_field==:id set by Node)
474             @ref.freeze
475           else
476             nil
477           end
478         end
479        
480         protected
481        
482         def check_inheritance
483           if @needs_inheritance_spread
484             spread_inheritance
485           end
486           true
487         end
488        
489         # When the rwp groups are changed, spread this change to the 'children' with
490         # inheritance mode set to '1'. 17.2s
491         # FIXME: make a single pass for spread_inheritance and update section_id and project_id ?
492         # FIXME: should also remove cached pages...
493         def spread_inheritance(i = self[:id])
494           base_class.connection.execute "UPDATE nodes SET rgroup_id='#{rgroup_id}', wgroup_id='#{wgroup_id}', pgroup_id='#{pgroup_id}', skin='#{skin}' WHERE #{ref_field(false)}='#{i}' AND inherit='1'"
495           ids = nil
496           base_class.with_exclusive_scope do
497             ids = base_class.fetch_ids("SELECT id FROM #{base_class.table_name} WHERE #{ref_field(true)} = '#{i.to_i}' AND inherit='1'")
498           end
499          
500           ids.each { |i| spread_inheritance(i) }
501         end
502        
503         # return the maximum status of the current node and all it's heirs. This is used to allow
504         # inheritance change with 'manage' rights on private nodes
505         def max_status_with_heirs(max=0)
506           max = [max, max_status].max
507           return max if max == Zena::Status[:pub]
508           heirs.each do |h|
509             max = [max, h.max_status_with_heirs(max)].max
510             break if max == Zena::Status[:pub]
511           end
512           return max
513         end
514        
515         private
516        
517         # Version of the node in DB (returns nil for new records)
518         def old
519           if new_record?
520             nil
521           else
522             @old ||= secure_drive(self.class) { self.class.find(self[:id]) }
523           end
524         end
525        
526         # List of elements using the current element as a reference. Used to update
527         # the rwp groups if they inherit from the reference. Can be overwritten by sub-classes.
528         def heirs
529           base_class.with_exclusive_scope do
530             base_class.find(:all, :conditions=>["#{ref_field(true)} = ? AND inherit='1'" , self[:id] ] ) || []
531           end
532         end
533        
534         # Reference class. Must be overwritten by sub-classes.
535         def ref_class
536           self.class
537         end
538        
539         # Must be overwritten.
540         def base_class
541           self.class
542         end
543        
544         # Reference foreign_key. Can be overwritten by sub-classes.
545         def ref_field(for_heirs=false)
546           :reference_id
547         end
548
549         public
550
551         # helper for testing validations
552         def show_errors
553           errors.each {|k,m| puts "[#{k}] #{m}"}
554         end
555        
556         module ClassMethods
557           # kpath is a class shortcut to avoid tons of 'OR type = Page OR type = Document'
558           # we build this path with the first letter of each class. The example bellow
559           # shows how the kpath is built:
560           #           class hierarchy
561           #                Node --> N           
562           #       Note --> NN          Page --> NP
563           #                    Document   Form   Section
564           #                       NPD      NPF      NPP
565           # So now, to get all Pages, your sql becomes : WHERE kpath LIKE 'NP%'
566           # to get all Documents : WHERE kpath LIKE 'NPD%'
567           # all pages without Documents : WHERE kpath LIKE 'NP%' AND NOT LIKE 'NPD%'
568           def kpath
569             @@kpath[self] ||= superclass == ActiveRecord::Base ? ksel : (superclass.kpath + ksel)
570           end
571          
572           # 'from' and 'joins' are removed: this method is used when receiving calls from zafu. Changing the source table removes
573           # the secure scope.
574           def clean_options(options)
575             options.reject do |k,v|
576               ! [ :conditions, :select, :include, :offset, :limit, :order, :lock ].include?(k)
577             end
578           end
579          
580           # kpath selector for the current class
581           def ksel
582             self.to_s[0..0]
583           end
584
585           @@kpath = {}
586
587           # Replace Rails subclasses normal behavior
588           def type_condition
589             " #{table_name}.kpath LIKE '#{kpath}%' "
590           end
591         end
592       end
593     end
594    
595     # ============================================= SECURE  ===============
596     module Secure
597       # protect access to site_id : should not be changed by users
598       def site_id=(i)
599         raise Zena::AccessViolation, "#{self.class.to_s} '#{self.id}': tried to change 'site_id' to '#{i}'."
600       end
601
602       # Set current visitor
603       def visitor=(visitor)
604         @visitor = visitor
605       end
606      
607       # these methods are not actions that can be called from the web !!
608       protected
609         # secure find with scope (for read/write or publish access).
610         def secure_with_scope(klass, find_scope, opts={})
611           if ((klass.send(:scoped_methods)[0] || {})[:create] || {})[:visitor]
612             # we are already in secure scope: this scope is the new 'exclusive' scope.
613             last_scope = klass.send(:scoped_methods).shift
614           end
615
616           scope = {:create => { :visitor => visitor }}
617           if klass.ancestors.include?(User)
618             scope[:find] ||= {}
619             ptbl = Participation.table_name
620             scope[:find][:joins] = "INNER JOIN #{ptbl} ON #{klass.table_name}.id = #{ptbl}.user_id AND #{ptbl}.site_id = #{visitor.site[:id]}"
621             scope[:find][:readonly]   = false
622             scope[:find][:select]     = "#{User.table_name}.*"
623             scope[:find][:conditions] = find_scope
624           elsif klass.column_names.include?('site_id')
625             if find_scope && !opts[:site_id_clause_set]
626               find_scope = "(#{find_scope}) AND (#{klass.table_name}.site_id = #{visitor.site[:id]})"
627             elsif !opts[:site_id_clause_set]
628               find_scope = "#{klass.table_name}.site_id = #{visitor.site[:id]}"
629             end
630             scope[:find] = { :conditions => find_scope }
631           elsif klass.ancestors.include?(Site)
632             # TODO: write tests
633             scope[:find] ||= {}
634             ptbl = Participation.table_name
635             scope[:find][:joins] = "INNER JOIN #{ptbl} ON #{klass.table_name}.id = #{ptbl}.site_id AND #{ptbl}.user_id = #{visitor[:id]} AND #{ptbl}.status = #{User::Status[:admin]}"
636             scope[:find][:readonly]   = false
637             scope[:find][:select]     = "#{Site.table_name}.*"
638             scope[:find][:conditions] = find_scope
639           end
640          
641           result = klass.with_scope( scope ) { yield }
642          
643           klass.send(:scoped_methods).unshift last_scope if last_scope
644          
645           secure_result(klass,result)
646         end
647      
648         def secure_result(klass,result)
649           if result && result != []
650             if klass.ancestors.include?(Node)
651               if result.kind_of? Array
652                 result.each {|r| visitor.visit(r) }
653               else
654                 visitor.visit(result)
655               end
656             end
657             result
658           else
659             nil
660           end
661         end
662        
663         # Secure for read/create.
664         # [read]
665         # * super user
666         # * owner
667         # * members of +read_group+ if the node is published and the current date is greater or equal to the publication date
668         # * members of +publish_group+ if +max_status+ >= prop
669         # The options hash is used internally by zena when maintaining parent to children inheritance and should not be used for other purpose if you do not want to break secure access.
670         def secure(klass, opts={}, &block)
671           if opts[:secure] == false
672             yield
673           elsif klass.ancestors.include?(Zena::Acts::SecureNode::InstanceMethods) && !visitor.is_su? # not super user
674             secure_with_scope(klass, secure_scope(klass.table_name), :site_id_clause_set => true, &block)
675           else
676             secure_with_scope(klass, nil, &block)
677           end
678         rescue ActiveRecord::RecordNotFound
679           # Rails generated exceptions
680           # TODO: monitor how often this happens and replace the finders concerned
681           nil
682         end
683        
684         def secure!(klass, opts={}, &block)
685           unless res = secure(klass, opts={}, &block)
686             raise ActiveRecord::RecordNotFound
687           end
688           res
689         end
690        
691         # Secure scope for read/create
692         def secure_scope(table_name)
693           if visitor.is_su?
694             "#{table_name}.site_id = #{visitor.site.id}"
695           else
696             # site_id AND... OWNER
697             "#{table_name}.site_id = #{visitor.site.id} AND (#{table_name}.user_id = '#{visitor[:id]}' OR "+
698             # OR READER if published
699             "(#{table_name}.rgroup_id IN (#{visitor.group_ids.join(',')}) AND #{table_name}.publish_from <= now() ) OR " +
700             # OR publisher if status is <> red
701             "(#{table_name}.pgroup_id IN (#{visitor.group_ids.join(',')}) AND #{table_name}.max_status <> #{Zena::Status[:red]}))"
702           end
703         end
704        
705
706         # Secure scope for write access.
707         # [write]