consent 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +13 -0
- data/Gemfile +4 -0
- data/README.md +245 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/consent.gemspec +25 -0
- data/lib/consent/ability.rb +13 -0
- data/lib/consent/action.rb +19 -0
- data/lib/consent/dsl.rb +35 -0
- data/lib/consent/permission.rb +29 -0
- data/lib/consent/permissions.rb +37 -0
- data/lib/consent/railtie.rb +19 -0
- data/lib/consent/rspec.rb +71 -0
- data/lib/consent/subject.rb +21 -0
- data/lib/consent/version.rb +3 -0
- data/lib/consent/view.rb +22 -0
- data/lib/consent.rb +37 -0
- data/lib/generators/consent/permissions_generator.rb +12 -0
- data/lib/generators/consent/templates/permissions.rb.erb +18 -0
- metadata +135 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 83520d7812046af394c1c5828f8456e9759a1984
|
4
|
+
data.tar.gz: 9d1998404b43857b3b8b4660ad88601fa96c5b01
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 57045196699fca95ab86ce0a5d293201d43a4287dbe621d46456afa6933ba71a55275a0dfd44aae090744536a48c03de0e6fd256b09c4e0e09a93ce106ed243b
|
7
|
+
data.tar.gz: 1fab9a82df666af3127df1c9cb892f9e06fa0f842581841c82840150f46ff0753bff8fa587e4285d7fc7024e7912a69f946dc88e5af63dc82952a593b40cefda
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
sudo: false
|
2
|
+
language: ruby
|
3
|
+
rvm:
|
4
|
+
- 2.2.2
|
5
|
+
before_install: gem install bundler -v 1.12.1
|
6
|
+
deploy:
|
7
|
+
provider: rubygems
|
8
|
+
api_key:
|
9
|
+
secure: aCipdg6IqKaxQmWQXFlqI2JqGOrCAxzYlutOn6nCSk9VCzmlRjDoEp1M99ASvmF5CP3KQgfRq8bxfLql8jss8tymK0u2ExvKaUglK0zb2KoLQDGDGuHw3RxCTnaxPBuO4/2PuS+nso6IcdqUaGOzh8FA7ePKPBmxl3kyNtArEDV88Eyx6tQYs1/1w153bAeBk57nTiu8CPS+dIWg+guQADTLPZ61fH4xKjPWXnr65pFF7YtU5YdPBHFkqL60X384OZj1c7ZTGvFj70+36oo617HigSg8HCE+E0R1N4JQ8/5xo2yHBdrtgsAqYyYWRgm1C6qu+T6yKMuokdoSd0Ji3Rigz6VRrOJfjmQQkwMnno9fCY3vND62zOb9Ow+MCNuQYSgNHc+YPURKmT05wadG0FpZdmo6hstPusleuG89NxnB/s2YjR0QWQK05MW1uhFdru1S2gBd1fMH1GLoThhdfGG1UJSkKOipUJyRupFaB9zimJO3HwaTP0Q+wP8MGZDAqbyzQ3bJSZaQmJC6loC2EtxxnOQxUIxaySLtNEU2LOr9IijExj1ldXkz8X1fIWwRr6BPnIeChFnuST+L9UDYtqk1WgQTUs+tOAxUjepAWhtIUL5h3Jg2NZd9RxPXq8IngDWZJwT8yA3E9wloQx5JzTIMas5wHwCQJuwjjk5ibss=
|
10
|
+
gem: consent
|
11
|
+
on:
|
12
|
+
tags: true
|
13
|
+
repo: powerhome/consent
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
# Consent [](https://travis-ci.org/powerhome/consent)
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
Add this line to your application's Gemfile:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem 'consent'
|
9
|
+
```
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install consent
|
18
|
+
|
19
|
+
## What is Consent
|
20
|
+
|
21
|
+
Consent makes defining permissions easier by providing a clean, concise DSL for authorization so that all abilities do not have to be in your `Ability`
|
22
|
+
class.
|
23
|
+
|
24
|
+
Consent takes application permissions and models them so that permissions are organized and can be defined granularly. It does so using the
|
25
|
+
following models:
|
26
|
+
|
27
|
+
* View: A collection of objects limited by a given condition.
|
28
|
+
* Action: An action performed on top of the objects limited by the view. For example, one user could only `:view` something, while another could `:manage` it.
|
29
|
+
* Subject: Holds the scope of the actions.
|
30
|
+
* Permission: What is given to the user. Combines a subject, an action and
|
31
|
+
a view.
|
32
|
+
|
33
|
+
## What Consent Is Not
|
34
|
+
|
35
|
+
Consent isn't a tool to enforce permissions -- it is intended to be used with CanCanCan and is only to make permissions more easily readable and definable.
|
36
|
+
|
37
|
+
## Subject
|
38
|
+
|
39
|
+
The subject is the central point of a group of actions and views. It will typically
|
40
|
+
be an `ActiveRecord` class, a `:symbol`, or any Plain Old Ruby Object.
|
41
|
+
|
42
|
+
You define a subject with the following DSL:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
Consent.define Project, 'Our Projects' do
|
46
|
+
#in this case, Project is the subject
|
47
|
+
# and `Our Projects` is the description that makes it clear to users
|
48
|
+
# what the subject is acting upon.
|
49
|
+
…
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
The scope is the action that's being performed on the subject. It can be anything, but will typically be an ActiveRecord class, a `:symbol`, or a PORO.
|
54
|
+
|
55
|
+
For instance:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
Consent.define :features, 'Beta Features' do
|
59
|
+
# whatever you put inside this method defines the scope
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
## Views
|
64
|
+
|
65
|
+
Views are the rules that limit the access to actions. For instance,
|
66
|
+
a user may see a `Project` from his department, but not from others. That rule
|
67
|
+
could be enforced with a `:department` view, defined like this:
|
68
|
+
|
69
|
+
### Hash Conditions
|
70
|
+
|
71
|
+
This is probably the most commonly used and is useful, for example,
|
72
|
+
when the view can be defined using a where condition in an ActiveRecord context.
|
73
|
+
It follows a match condition and will return all objects that meet the criteria
|
74
|
+
and is based off a boolean:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
Consent.define Project, 'Projects' do
|
78
|
+
view :department, "User's department only" do |user|
|
79
|
+
{ department_id: user.id }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
Although hash conditions (matching object's attributes) are recommended,
|
85
|
+
the constraints can be anything you want. Since Consent does not enforce the
|
86
|
+
rules, those rules are directly given to CanCan. Following [CanCan rules](https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities%3A-Best-Practice)
|
87
|
+
for defining abilities is recommended.
|
88
|
+
|
89
|
+
### Object Conditions
|
90
|
+
|
91
|
+
If you're not matching for equal values, then you would need to use an object
|
92
|
+
condition, which matches data based off a range.
|
93
|
+
|
94
|
+
If you already have an object and want to check to see whether the user has
|
95
|
+
permission to view that specific object, you would use object conditions.
|
96
|
+
|
97
|
+
If your needs can't be satisfied by hash conditions, it is recommended that a
|
98
|
+
second condition is given for constraining object instances. For example, if you
|
99
|
+
want to restrict a view for smaller volume projects:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
Consent.define Project, 'Projects' do
|
103
|
+
view :small_volumes, "User's department only",
|
104
|
+
-> (user) {
|
105
|
+
['amount < ?', user.volume_limit]
|
106
|
+
end,
|
107
|
+
-> (user, project) {
|
108
|
+
project.amount < user.volume_limit
|
109
|
+
}
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
For object conditions, the latter argument will be the referred object, while the
|
114
|
+
former will be the context given to the [Permission](#permission) (also check
|
115
|
+
[CanCan integration](#cancan-integration)).
|
116
|
+
|
117
|
+
## Action
|
118
|
+
|
119
|
+
An action is anything you can perform on a given subject. In the example of
|
120
|
+
Features this would look like the following using Consent's DSL:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
Consent.define :features, 'Beta Features' do
|
124
|
+
action :beta_chat, 'Beta Chat App'
|
125
|
+
end
|
126
|
+
```
|
127
|
+
|
128
|
+
To associate different views to the same action:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
Consent.define Project, 'Projects' do
|
132
|
+
# returns conditions that can be used as a matcher for objects so the matcher
|
133
|
+
# can return true or false (hash version)
|
134
|
+
view :department, "User's department only" do |user|
|
135
|
+
{ department_id: user.id }
|
136
|
+
end
|
137
|
+
view :future_projects, "User's department only",
|
138
|
+
# returns a condition to be applied to a collection of objects
|
139
|
+
-> (_) {
|
140
|
+
['starts_at > ?', Date.today]
|
141
|
+
end,
|
142
|
+
# returns true/false based on a condition -- to use this, you must pass in
|
143
|
+
# an instance of an object in order to check the permission
|
144
|
+
-> (user, project) {
|
145
|
+
project.starts_at > Date.today
|
146
|
+
}
|
147
|
+
|
148
|
+
action :read, 'Read projects', views: [:department, :future_projects]
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
If you have a set of actions with the same set of views, you can use a
|
153
|
+
`with_defaults` block to simplify the writing:
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
with_defaults views: [:department, :small_volumes] do
|
157
|
+
action :read, 'Read projects'
|
158
|
+
action :approve, 'Approve projects'
|
159
|
+
end
|
160
|
+
```
|
161
|
+
|
162
|
+
## Permission
|
163
|
+
|
164
|
+
A permission is what is consented to the user. It is the *permission* to perform
|
165
|
+
an *action* on a limited *view* of the *subject*. It marries the three concepts
|
166
|
+
to consent an access to the user.
|
167
|
+
|
168
|
+
A permission is not specified by the user, it is calculated from a permissions
|
169
|
+
hash owned by a `User`, or a `Role` on an application.
|
170
|
+
|
171
|
+
The permissions hash looks like the following:
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
{
|
175
|
+
project: {
|
176
|
+
read: 'department',
|
177
|
+
approve: 'small_volumes'
|
178
|
+
}
|
179
|
+
}
|
180
|
+
```
|
181
|
+
|
182
|
+
In other words:
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
{
|
186
|
+
<subject>: {
|
187
|
+
<action>: <view>
|
188
|
+
}
|
189
|
+
}
|
190
|
+
```
|
191
|
+
|
192
|
+
### Full Access
|
193
|
+
|
194
|
+
Full (unrestricted by views) access is granted when view is `'1'`, `true` or
|
195
|
+
`'true'`. For instance:
|
196
|
+
|
197
|
+
In other words:
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
{
|
201
|
+
projects: {
|
202
|
+
approve: true
|
203
|
+
}
|
204
|
+
}
|
205
|
+
```
|
206
|
+
|
207
|
+
## CanCan Integration
|
208
|
+
|
209
|
+
Consent provides a CanCan ability (Consent::Ability) to integrate your
|
210
|
+
permissions with frameworks like Rails. To use it with Rails check out the
|
211
|
+
example at [Ability for Other Users](https://github.com/CanCanCommunity/cancancan/wiki/Ability-for-Other-Users)
|
212
|
+
on CanCanCan's wiki.
|
213
|
+
|
214
|
+
In the ability you define the scope of the permissions. This is typically an
|
215
|
+
user:
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
Consent::Ability.new(user.permissions, user)
|
219
|
+
```
|
220
|
+
|
221
|
+
The first parameter given to the ability is the permissions hash, seen at
|
222
|
+
[Permission](#permission). The following parameters are the permission context.
|
223
|
+
These parameters are given directly to the condition blocks defined by the views
|
224
|
+
in the exact same order, so it's up to you to define what your context is.
|
225
|
+
|
226
|
+
## Rails Integration
|
227
|
+
|
228
|
+
Consent is integrated into Rails with `Consent::Railtie`. To define where
|
229
|
+
your permission files will be, use `config.consent.path`. This defaults to
|
230
|
+
`app/permissions/` to conform to Rails' standards.
|
231
|
+
|
232
|
+
## Development
|
233
|
+
|
234
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
235
|
+
`rake spec` to run the tests. You can also run `bin/console` for an interactive
|
236
|
+
prompt that will allow you to experiment.
|
237
|
+
|
238
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
239
|
+
release a new version, update the version number in `version.rb`, and then run
|
240
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
241
|
+
git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
242
|
+
|
243
|
+
## Contributing
|
244
|
+
|
245
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/powerhome/consent.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "consent"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/consent.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'consent/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'consent'
|
8
|
+
spec.version = Consent::VERSION
|
9
|
+
spec.authors = ['Carlos Palhares']
|
10
|
+
spec.email = ['chjunior@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = 'Consent'
|
13
|
+
spec.description = 'Consent'
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split.reject do |file|
|
16
|
+
file =~ /^(test|spec|features)/
|
17
|
+
end
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_development_dependency 'activesupport', '~> 3.2.22'
|
21
|
+
spec.add_development_dependency 'cancancan', '~> 1.15.0'
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.12'
|
23
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
24
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
25
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Consent
|
2
|
+
class Ability
|
3
|
+
include CanCan::Ability
|
4
|
+
|
5
|
+
def initialize(permissions, *args)
|
6
|
+
Consent.permissions(permissions).each do |permission|
|
7
|
+
conditions = permission.conditions(*args)
|
8
|
+
object_conditions = permission.object_conditions(*args)
|
9
|
+
can permission.action_key, permission.subject_key, conditions, &object_conditions
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Consent
|
2
|
+
class Action
|
3
|
+
attr_reader :key, :label, :options
|
4
|
+
|
5
|
+
def initialize(key, label, options = {})
|
6
|
+
@key = key
|
7
|
+
@label = label
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def view_keys
|
12
|
+
@options.fetch(:views, [])
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_view
|
16
|
+
@options[:default_view]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/consent/dsl.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Consent
|
2
|
+
class DSL
|
3
|
+
attr_reader :subject
|
4
|
+
|
5
|
+
def initialize(subject, defaults)
|
6
|
+
@subject = subject
|
7
|
+
@defaults = defaults
|
8
|
+
end
|
9
|
+
|
10
|
+
def with_defaults(new_defaults, &block)
|
11
|
+
DSL.build(@subject, @defaults.merge(new_defaults), &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
# rubocop:disable Link/UnusedBlockArgument, Link/Eval
|
15
|
+
def eval_view(key, label, collection_conditions)
|
16
|
+
view key, label do |user|
|
17
|
+
eval(collection_conditions)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
# rubocop:enable Link/UnusedBlockArgument, Link/Eval
|
21
|
+
|
22
|
+
def view(key, label, instance = nil, collection = nil, &block)
|
23
|
+
collection ||= block
|
24
|
+
@subject.views[key] = View.new(key, label, instance, collection)
|
25
|
+
end
|
26
|
+
|
27
|
+
def action(key, label, options = {})
|
28
|
+
@subject.actions << Action.new(key, label, @defaults.merge(options))
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.build(subject, defaults = {}, &block)
|
32
|
+
DSL.new(subject, defaults).instance_eval(&block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Consent
|
2
|
+
class Permission
|
3
|
+
def initialize(subject, action, view = nil)
|
4
|
+
@subject = subject
|
5
|
+
@action = action
|
6
|
+
@view = view
|
7
|
+
end
|
8
|
+
|
9
|
+
def subject_key
|
10
|
+
@subject.key
|
11
|
+
end
|
12
|
+
|
13
|
+
def action_key
|
14
|
+
@action.key
|
15
|
+
end
|
16
|
+
|
17
|
+
def view_key
|
18
|
+
@view && @view.key
|
19
|
+
end
|
20
|
+
|
21
|
+
def conditions(*args)
|
22
|
+
@view && @view.conditions(*args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def object_conditions(*args)
|
26
|
+
@view && @view.object_conditions(*args)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Consent
|
2
|
+
class Permissions
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(permissions)
|
6
|
+
@permissions = permissions
|
7
|
+
end
|
8
|
+
|
9
|
+
def each(&block)
|
10
|
+
Consent.subjects.values.each do |subject|
|
11
|
+
subject.actions.map do |action|
|
12
|
+
map_permission subject, action
|
13
|
+
end.compact.each(&block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def map_permission(subject, action)
|
20
|
+
subject_key = subject.permission_key
|
21
|
+
actions = @permissions[subject_key] || @permissions[subject_key.to_s]
|
22
|
+
view = actions && (actions[action.key] || actions[action.key.to_s])
|
23
|
+
full(subject, action, view) || partial(subject, action, view)
|
24
|
+
end
|
25
|
+
|
26
|
+
def full(subject, action, view_key)
|
27
|
+
return unless Consent::FULL_ACCESS.include?(view_key.to_s.strip)
|
28
|
+
Permission.new(subject, action)
|
29
|
+
end
|
30
|
+
|
31
|
+
def partial(subject, action, view_key)
|
32
|
+
view = subject.view_for(action, view_key.to_s.to_sym)
|
33
|
+
return if view.nil?
|
34
|
+
Permission.new(subject, action, view)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Consent
|
2
|
+
# Plugs consent permission load to the Rails class loading cycle
|
3
|
+
class Railtie < Rails::Railtie
|
4
|
+
config.before_configuration do
|
5
|
+
default_path = Rails.root.join('app', 'permissions')
|
6
|
+
config.consent = Struct.new(:paths).new([default_path])
|
7
|
+
end
|
8
|
+
|
9
|
+
config.to_prepare do
|
10
|
+
Consent.subjects.clear
|
11
|
+
Consent.load_subjects! Rails.application.config.consent.paths
|
12
|
+
end
|
13
|
+
|
14
|
+
config.after_initialize do
|
15
|
+
permissions_paths = config.consent.paths.map(&:to_s)
|
16
|
+
ActiveSupport::Dependencies.autoload_paths -= permissions_paths
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'consent'
|
2
|
+
|
3
|
+
module Consent
|
4
|
+
module Rspec
|
5
|
+
extend RSpec::Matchers::DSL
|
6
|
+
|
7
|
+
def get_view(subject_key, view_key)
|
8
|
+
Consent.subjects[subject_key].try(:views).try(:[], view_key)
|
9
|
+
end
|
10
|
+
|
11
|
+
def get_action(subject_key, action_key)
|
12
|
+
(Consent.subjects[subject_key].try(:actions) || []).find do |action|
|
13
|
+
action.key.eql?(action_key)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_action_view_keys(subject_key, action_key)
|
18
|
+
(get_action(subject_key, action_key).try(:options).try(:[], :views) || []).map(&:key)
|
19
|
+
end
|
20
|
+
|
21
|
+
matcher :consent_action do |action_key|
|
22
|
+
chain :with_views do |*views|
|
23
|
+
@views = views
|
24
|
+
end
|
25
|
+
|
26
|
+
match do |subject_key|
|
27
|
+
action = get_action(subject_key, action_key)
|
28
|
+
action && @views ? action.view_keys.sort.eql?(@views.sort) : !action.nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
failure_message do |subject_key|
|
32
|
+
action = get_action(subject_key, action_key)
|
33
|
+
message = "expected %s (%s) to provide action %s" % [
|
34
|
+
subject_key.to_s, subject.class, action_key
|
35
|
+
]
|
36
|
+
|
37
|
+
if action && @views
|
38
|
+
'%s with views %s, but actual views are %p' % [message, @views, action.view_keys]
|
39
|
+
else
|
40
|
+
message
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
matcher :consent_view do |view_key, conditions|
|
46
|
+
chain :to do |*context|
|
47
|
+
@context = context
|
48
|
+
end
|
49
|
+
|
50
|
+
match do |subject_key|
|
51
|
+
view = get_view(subject_key, view_key)
|
52
|
+
conditions ? view.try(:conditions, *@context).eql?(conditions) : !view.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
failure_message do |subject_key|
|
56
|
+
view = get_view(subject_key, view_key)
|
57
|
+
message = "expected %s (%s) to provide view %s with %p, but" % [
|
58
|
+
subject_key.to_s, subject.class, view_key, conditions
|
59
|
+
]
|
60
|
+
|
61
|
+
if view && conditions
|
62
|
+
actual_conditions = view.conditions(*@context)
|
63
|
+
'%s conditions are %p' % [message, actual_conditions]
|
64
|
+
else
|
65
|
+
actual_views = Consent.subjects[subject_key].try(:views).try(:keys)
|
66
|
+
'%s available views are %p' % [message, actual_views]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Consent
|
2
|
+
class Subject
|
3
|
+
attr_reader :key, :label, :actions, :views
|
4
|
+
|
5
|
+
def initialize(key, label)
|
6
|
+
@key = key
|
7
|
+
@label = label
|
8
|
+
@actions = []
|
9
|
+
@views = Consent.default_views.clone
|
10
|
+
end
|
11
|
+
|
12
|
+
def permission_key
|
13
|
+
ActiveSupport::Inflector.underscore(@key.to_s).to_sym
|
14
|
+
end
|
15
|
+
|
16
|
+
def view_for(action, key)
|
17
|
+
view = @views.keys & action.view_keys & [key]
|
18
|
+
@views[view.first] || @views[action.default_view]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/consent/view.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Consent
|
2
|
+
class View
|
3
|
+
attr_reader :key, :label
|
4
|
+
|
5
|
+
def initialize(key, label, instance = nil, collection = nil)
|
6
|
+
@key = key
|
7
|
+
@label = label
|
8
|
+
@instance = instance
|
9
|
+
@collection = collection
|
10
|
+
end
|
11
|
+
|
12
|
+
def conditions(*args)
|
13
|
+
return @collection unless @collection.respond_to?(:call)
|
14
|
+
@collection.call(*args)
|
15
|
+
end
|
16
|
+
|
17
|
+
def object_conditions(*args)
|
18
|
+
return @instance unless @instance.respond_to?(:curry)
|
19
|
+
@instance.curry[*args]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/consent.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'consent/version'
|
2
|
+
require 'consent/subject'
|
3
|
+
require 'consent/view'
|
4
|
+
require 'consent/action'
|
5
|
+
require 'consent/dsl'
|
6
|
+
require 'consent/permission'
|
7
|
+
require 'consent/permissions'
|
8
|
+
require 'consent/ability' if defined?(CanCan)
|
9
|
+
require 'consent/railtie' if defined?(Rails)
|
10
|
+
|
11
|
+
module Consent
|
12
|
+
FULL_ACCESS = %w(1 true).freeze
|
13
|
+
|
14
|
+
def self.default_views
|
15
|
+
@default_views ||= {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.subjects
|
19
|
+
@subjects ||= {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.load_subjects!(paths)
|
23
|
+
permission_files = paths.map { |dir| dir.join('*.rb') }
|
24
|
+
Dir[*permission_files].each(&Kernel.method(:load))
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.define(key, label, options = {}, &block)
|
28
|
+
defaults = options.fetch(:defaults, {})
|
29
|
+
subjects[key] = Subject.new(key, label).tap do |subject|
|
30
|
+
DSL.build(subject, defaults, &block)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.permissions(permissions)
|
35
|
+
Permissions.new(permissions)
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Consent
|
2
|
+
class PermissionsGenerator < Rails::Generators::NamedBase
|
3
|
+
source_root File.expand_path('../templates', __FILE__)
|
4
|
+
argument :description, type: :string, required: false
|
5
|
+
|
6
|
+
def create_permissions
|
7
|
+
template "permissions.rb.erb", "app/permissions/#{file_path}.rb", assigns: {
|
8
|
+
description: description
|
9
|
+
}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Consent.define <%= @name %>, "<%= @description %>" do
|
2
|
+
# Define your views
|
3
|
+
# i.e.:
|
4
|
+
# view :department, 'Same department only' do |user|
|
5
|
+
# { department_id: user.department_id }
|
6
|
+
# end
|
7
|
+
|
8
|
+
# Define your actions
|
9
|
+
# i.e.:
|
10
|
+
# action :read, "Can view <%= @subject %>"
|
11
|
+
|
12
|
+
# Define actions with different views
|
13
|
+
# i.e.:
|
14
|
+
# action :update, "Can edit existing <%= @subject %>", views: :department
|
15
|
+
|
16
|
+
# Find more examples at:
|
17
|
+
# https://github.com/powerhome/consent
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: consent
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Carlos Palhares
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-11-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.2.22
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.2.22
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: cancancan
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.15.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.15.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.12'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.12'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
description: Consent
|
84
|
+
email:
|
85
|
+
- chjunior@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
92
|
+
- ".travis.yml"
|
93
|
+
- Gemfile
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- bin/console
|
97
|
+
- bin/setup
|
98
|
+
- consent.gemspec
|
99
|
+
- lib/consent.rb
|
100
|
+
- lib/consent/ability.rb
|
101
|
+
- lib/consent/action.rb
|
102
|
+
- lib/consent/dsl.rb
|
103
|
+
- lib/consent/permission.rb
|
104
|
+
- lib/consent/permissions.rb
|
105
|
+
- lib/consent/railtie.rb
|
106
|
+
- lib/consent/rspec.rb
|
107
|
+
- lib/consent/subject.rb
|
108
|
+
- lib/consent/version.rb
|
109
|
+
- lib/consent/view.rb
|
110
|
+
- lib/generators/consent/permissions_generator.rb
|
111
|
+
- lib/generators/consent/templates/permissions.rb.erb
|
112
|
+
homepage:
|
113
|
+
licenses: []
|
114
|
+
metadata: {}
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options: []
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
requirements: []
|
130
|
+
rubyforge_project:
|
131
|
+
rubygems_version: 2.4.8
|
132
|
+
signing_key:
|
133
|
+
specification_version: 4
|
134
|
+
summary: Consent
|
135
|
+
test_files: []
|