DOCS-POLICIES.md in hyper-operation-0.5.12 vs DOCS-POLICIES.md in hyper-operation-0.99.0
- old
+ new
@@ -405,9 +405,102 @@
end
regulate_broadcast { |policy| policy.send_all.to(policy.teams) }
end
```
+## Regulating Scopes
+
+Consider the following expression (evaluated on the client)
+
+```ruby
+ Order.for_vip_customers.count
+```
+
+Even though the policy system will prevent us from looking into the actual attributes of any record, a malicioius hacker can find private information about our data if the above expression is not secured. Moreover a DOS attack could be formed by repeatedly attempting to perform a variant of `Order.all`.
+
+To prevent this scopes and relationships can also be regulated. A scope and relationship regulation is a proc that will return either a truthy or falsy value or calls the `denied!` method. The proc is evaluated in the context of the relationship object, and the `acting_user` method is available for the proc's use in making decisions.
+
+All the scopes in a chain are evaluated together, and permission is granted or denied as follows:
+
++ If *any* of the regulations in a chain of scopes calls `denied!` then the remote request is aborted;
++ If *any* of the regulations in a chain of scopes returns a truthy value then access is granted to the entire chain;
++ If *none* of the regulations in a chain of scopes returns a truthy value then the request is aborted.
+
+Example:
+
+```ruby
+class Order < ApplicationRecord
+ regulate_scope(:for_vip_customers) { denied! unless acting_user.admin? }
+ regulate_scope(:active) { acting_user.admin? }
+end
+
+class User < ApplicationRecord
+ regulate_relationship(:orders) { self == acting_user }
+end
+
+ # in component code
+
+ user.orders.count # valid if user is the acting user because the orders regulation returned true
+ # but will raise error if acting_user is not == user
+ user.orders.active.count # valid if user is the acting user or if current user is an administrator
+ user.orders.for_vip_customers # fails unless acting user is an admin
+```
+
+By default all relationships and scopes (including `all` and `unscoped` have a regulation that returns nil, so unless you explicitly provide a regulation that returns true, the client can not access any scopes.
+
+There are some short hand ways to define regulations as well:
+
+#### Constant Regulations
+
+If the regulation always does the same thing you can specify what to do without the block:
+
+```ruby
+ regulate_scope my_scope: :always_allow # any truthy value works
+ regulate_scope my_scope: :denied! # :deny or :denied work as well
+ regulate_relationship many_of_those: :denied! # works the same on relationships
+```
+
+Always denying a regulation effectively makes it inaccessible except on the server.
+
+Likewise be careful of always returning true for a scope, as this means that a hacker only needs
+to include this scope in the chain to gain access to the chain. So just make sure that scopes that return
+true, narrow the scope down to something you would not mind anybody seeing.
+
+For development you can easily access everything (except regulations that explicitly invoke denied!) simply by doing this:
+
+```ruby
+class ApplicationRecord < ActiveRecord::Base
+ regulate_scope all: :always_allow if Rails.env.development?
+ regulate_scope unscoped: :always_allow if Rails.env.development?
+end
+```
+
+#### Regulations directly on scopes and has_many relationships
+
+You can also directly add the regulation where you declare the scope or relationship using the `regulate:` option.
+
+```ruby
+ # here is a handy scope to add to ApplicationRecord that you can attach to
+ # any scope chain to give admin's full access
+ scope :admin, ->() {}, regulate: -> () { acting_user.admin? || denied! }
+
+ # customers can always see their orders, otherwise we return nil meaning "don't know yet"
+ has_many :orders, regulate: -> () { acting_user == self }
+```
+
+## Regulating server_method and finder_method methods
+
+The server or finder method proc will be executed in the context of the appropriate object (a record for server_method, and a relationship collection for finder_method.) Attached to this object will be the current `acting_user` method, and a `denied!` method.
+
+You can use these methods to restrict access to server and finder methods.
+
+```ruby
+ server_method :unit_cost do
+ denied! unless acting_user.admin? # only admin's can see this
+ # continue on calculating the unit cost
+ end
+```
+
## Browser Initiated Change policies
To allow code in the browser to create, update or destroy a model, there must be a change access policy defined for that operation.
Each change access policy executes a block in the context of the record that will be accessed. The current value of `acting_user` is also defined for the life of the block.