Module Access::RequirePrivilege::ClassMethods
In: lib/access_require_privilege.rb

Methods

Public Instance methods

Returns blocks of attributes to be set before others in attributes=

The problem we‘re trying to solve here is, say:

  Blog.create :owner => ..., :name => ...

where the name attribute is guarded by require_privilege :to_update. If :owner is not set first, the permission check will fail. So, we have to set up the access control keys before the other attributes.

(In general, it‘s possible for an attribute to be both settable and an access control key — imagine a "published" flag which governs read access for some users, and a "publish" privilege which allows an editor to set the attribute. In this case, we‘d want two blocks of "early privileges", one for the access control keys that determine whether you can publish, and one for the published flag itself, followed by the ordinary attributes).

[Source]

     # File lib/access_require_privilege.rb, line 498
498:       def attribute_block_set_groups
499:         [['owner', 'owner_id', 'owner_firm', 'owner_firm_id']]
500:       end

Puts the operations on a list of known privileges for the class which are controlled by the RBAC system. Privileges will typically name an operation or set of operations.

The list may be retrieved by calling Klass.declared_privileges

[Source]

    # File lib/access_require_privilege.rb, line 46
46:       def declare_privilege *args
47:         args.each do |arg| 
48:           if !arg.is_a?( Symbol )
49:             raise ArgumentError, 
50:               "Attempt to declare #{arg.inspect}, not a symbol, " +
51:               "as a privilege"
52:           end
53:           @declared_privileges ||= DEFAULT_DECLARED_PRIVILEGES.dup
54:           @declared_privileges << arg unless 
55:               @declared_privileges.include? arg
56:         end
57:       end

Returns an array of operations (as symbols) which have been declared as permissioned operations for this class, via declare_permission or require_privilege

[Source]

    # File lib/access_require_privilege.rb, line 63
63:       def declared_privileges
64:         @declared_privileges ||= DEFAULT_DECLARED_PRIVILEGES.dup
65:         @declared_privileges
66:       end

[Source]

     # File lib/access_require_privilege.rb, line 567
567:       def dissociate_privilege( foreign_class_name, association_name )
568:         reflected_privilege( :dissociate,
569:                              foreign_class_name + '#' + association_name.to_s )
570:       end

Supported arguments are as for require_privilege (q.v.), but this function arranges that the permission check always fails. So, for instance,

  never_permit_anyone :to_update_attribute => :name

would prevent anyone from changing the name of a record that had already been saved, and

  never_permit_anyone :at_callback => :before_update

would keep any preexisting record from ever getting updated at all.

[Source]

     # File lib/access_require_privilege.rb, line 267
267:       def never_permit_anyone key_args
268:         valid_keys = [:to_update_attribute, :to_initialize_attribute, 
269:                       :at_callback, :for_action]
270:         key_args.each do |k,v|
271:           unless valid_keys.include?( k )
272:             raise ArgumentError, 
273:               "#{k.inspect} not a valid keyword for never_permit_anyone"
274:           end
275:         end
276:         require_privilege_internal :forbidden_operation, key_args
277:       end

Returns true if the user could ever create an object of this class.

Ordinarily checks two ways that permission could be denied:

  • The class may +:require_privilege … :for_action => create+ (or equivalently, +:at_callback => :before_create+).

    If so, we require that the user has that required permission.

  • The class may have foreign keys declared with +:null => false+ and with a +:to_associate+ permission on the foreign side.

[Source]

     # File lib/access_require_privilege.rb, line 515
515:       def permits_create?( user = User.current )
516: 
517:         # Check :before_create callback on this class, if any
518: 
519:         priv = self.callback_privilege( :before_create )
520: 
521:         unless priv.nil?
522:           if !priv.is_a?( Array )
523:             klass = self
524:           else
525:             association = class_for_associate( priv.last )
526:             priv = priv.first
527:           end
528: 
529:           return false unless user.could_ever?( priv, klass )
530:         end
531: 
532:         # Check associates...
533: 
534:         reflect_on_all_associations( :belongs_to ).each do |assoc|
535: 
536:           foreign_key = assoc.primary_key_name.to_s # sigh...
537:           column_desc = columns.detect { |col| col.name == foreign_key }
538: 
539:           if !column_desc.null
540:             klass      = class_for_associate( assoc.name )
541:             assoc_priv = klass.associate_privilege( self.name, assoc.name )
542:             return false if !assoc_priv.nil? && 
543:                             !user.could_ever?( assoc_priv, klass )
544:           end
545:           
546:         end
547: 
548:         # All checks passed
549: 
550:         return true
551: 
552:       end

An abbreviation for common uses of +require_privilege :for_action+.

  require_eponymous_privilege_to :create, :destroy

is just shorthand for

  require_privilege :create,  :for_action => :create
  require_privilege :destroy, :for_action => :destroy

[Source]

     # File lib/access_require_privilege.rb, line 402
402:       def require_eponymous_privilege_to *args
403: 
404:         args.each do |arg|
405:           callback = EVENT_CALLBACK_KEYS[ arg ]
406:           if callback.nil?
407:             raise ArgumentError, 
408:               "require_eponymous_privilege can't handle #{arg}"
409:           end
410:           require_privilege arg, :at_callback => callback
411:         end
412:         
413:       end

require_privilege’ arranges to auto-generate wrapper code which will cause permissions to be checked before the model code performs certain actions.

The generated code usually checks for permissions declared on the object itself. However, :on_associated requests permission checks be performed on some associated object, named by an attribute. Thus for instance,

  class Post
    belongs_to :blog
    require_privilege :edit_post, :on_associated => :blog,
      :to_set_attribute => [:text]
    ...
  end

will prohibit setting the text of a post unless the user has an ‘edit_post’ permission on the associated blog. If the association isn‘t set up, all such checks fail. (Without :on_associated, this would check for the :edit_post privilege on posts).

Other keywords add specific restrictions, as follows:

:to_invoke
"method or methods" is a list of symbols naming instance methods of the class (or just a symbol, again naming an instance method). Arranges that before invoking those methods, we first do
  self.check_permission! :privilege

throwing the PermissionFailure exception if User.current does not have permission to perform the operation.

This may not work for private methods; that‘s not a bug, as this is intended to guard public APIs.

:to_set_attribute
value is the name of a single attribute (as a symbol, e.g. :name), or a list of attributes (e.g., [:name, :review_date, :status]). This arranges to put check_permission! guards on invocations of the attribute‘s setter methods (e.g., :name=, :review_date=, :status=).

Note that attempting to set an attribute to the value it already has will not result in a permissions check. (There are points at which Rails does this internally, which would yield spurious permission failures if we did these checks).

:to_update_attribute
As :to_set_attribute, but only does the checks for saved objects (those for which :new_record? returns false). So, for instance,
  require_privilege :edit_post, :to_update_attribute => :entry_txt

would require the :edit_post privilege only if someone is trying to update the text of an existing post, but would allow them to initialize the text of a new, unsaved one.

:to_initialize_attribute
The opposite of :to_update_attribute — permission checks are performed only when the attribute(s) are set on unsaved objects.
:to_access_attribute
guards all writes (as :to_set_attribute), and reads as well, e.g. by obj.attr, obj.attr?, etc.

All of these work on attributes which shadow database columns, and those that don‘t (e.g., declared by attr_accessor). The only known bug is that if you‘re using composed_of, you should guard the composite pseudo-attribute, not the component attributes corresponding to individual database columns.

:to_associate_as, :to_dissociate_as
These next two options add permission checks on operations on a different class. A typical case would be
   class BlogEntry
     include Access::Controlled
     belongs_to :blog
   end

   class Blog
     include Access::Controlled
     require_privilege :post,
        :to_associate_as  => ['BlogEntry#blog']
     require_privilege :delete_post,
        :to_dissociate_as => ['BlogEntry#blog']
   end

This causes the following permission checks:

  • when a blog is assigned to a blog_entry, as by, e.g.,
       BlogEntry.new    :blog => some_blog, ...
       BlogEntry.create :blog => some_blog, ...
       blog_entry.blog = some_blog
       blog_entry.blog_id = params[...][:blog_id]
    

    :to_associate_as arranges that we first do some_blog.check_permission!( :post ), before actually changing the association. (For the blog_id case, this is actually implemented by a check within the database, to avoid the full expense of loading the object).

  • when some_blog is already the blog of some_entry, and we do any of
       some_entry.blog = some_other_blog
       some_entry.update_attribute :blog, some_other_blog
       some_entry.destroy
    

    :to_dissociate_as arranges that we first do some_blog.check_permission!( :delete_post ), before actually performing the operation.

These options are not compatible with :on_associated.

:at_callback
Arranges for a privilege check whenever the given active record callback is invoked. Most standard callbacks should be supported. See the Rails docs for available callbacks, or require_eponymous_privilege_to and +require_privilege :for_action+, q.v., which provide more readable versions of the usual use cases for this.
:for_action
Arranges that at the named points of the object lifecycle, (say, :create) we perform a permission check to see if the user has the privilege of the same name (that is, if there‘s a permission that grants him :create privilege on the record).

The lifecycle points are:

+:create+
first save of a new object, after its attributes have all been set. (You can create an object with any attribute values you like in core, but saving it and making it available to others requires a permission check)
+:find+
We check for +:find+ permission after retrieving an object from the database. The overhead can be considerable for queries returning large numbers of records.
+:update+
save of an object that already existed in the db.
+:destroy+
what it says.

These are implemented by hanging permission checks on the +:before_create+, +:after_find+, +:before_update+, and +:before_destroy+ active record callbacks, using +require_privilege :at_callback+.

[Source]

     # File lib/access_require_privilege.rb, line 238
238:       def require_privilege privilege, key_args
239: 
240:         declare_privilege privilege unless key_args[ :on_associated ]
241:         require_privilege_internal privilege, key_args
242: 
243:       end

[Validate]