simon_says 0.1.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +38 -38
- data/lib/generators/active_record/simon_says_generator.rb +23 -0
- data/lib/generators/active_record/templates/migration.rb +5 -0
- data/lib/simon_says/authorizer.rb +6 -8
- data/lib/simon_says/roleable.rb +4 -7
- data/lib/simon_says/version.rb +1 -1
- data/simon_says.gemspec +1 -1
- data/test/rails_app/app/controllers/documents_controller.rb +2 -2
- data/test/rails_app/db/schema.rb +11 -11
- data/test/simon_says/roleable_test.rb +4 -4
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c3b6bd7983eb136f044c2653430143493637871
|
4
|
+
data.tar.gz: a66d00fe12a96f48e8865a61aaf61f145feb5007
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 284e3a2a74a36d1ae52aa78849a347f38071b4c5838dc43b60cdba794fd9bed021054941b44b44d28744511a3af57eb6764108ae087b7bf1d17e96b03f1f1e8a
|
7
|
+
data.tar.gz: dea0bc94572281d6b505ef4a3eb2ed0fb502e916483e122cf19f34444dfaf12947e374065495f2b2edd3c8a0e370b45a5454a1075c56daec896f642a02ae451e
|
data/README.md
CHANGED
@@ -4,22 +4,12 @@
|
|
4
4
|
Logo](https://raw.githubusercontent.com/SimplyBuilt/SimonSays/master/SimonSays.png)
|
5
5
|
|
6
6
|
This gem is a simple, declarative, role-based access control system for
|
7
|
-
Rails that works great with devise!
|
8
|
-
[docs](http://www.rubydoc.info/github/SimplyBuilt/SimonSays/) for more
|
9
|
-
details.
|
7
|
+
Rails that works great with devise!
|
10
8
|
|
11
9
|
[![Travis Build Status](https://travis-ci.org/SimplyBuilt/SimonSays.svg)](https://travis-ci.org/SimplyBuilt/SimonSays)
|
12
10
|
[![Gem Version](https://badge.fury.io/rb/simon_says.svg)](https://badge.fury.io/rb/simon_says)
|
13
11
|
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
|
14
12
|
|
15
|
-
## About
|
16
|
-
|
17
|
-
A ruby gem for simple, declarative, role-based access control system for
|
18
|
-
[Rails](https://github.com/rails/rails) that works great with
|
19
|
-
[Devise](https://github.com/plataformatec/devise)! Take a look at the
|
20
|
-
[docs](http://www.rubydoc.info/github/SimplyBuilt/SimonSays/) for more
|
21
|
-
details!
|
22
|
-
|
23
13
|
### Installation
|
24
14
|
|
25
15
|
SimonSays can be installed via your Gemfile or using Ruby gems directly.
|
@@ -32,23 +22,24 @@ gem 'simon_says'
|
|
32
22
|
|
33
23
|
SimonSays consists of two parts:
|
34
24
|
|
35
|
-
1. A [Roleable](#roleable) concern provides a way to define access roles
|
36
|
-
on
|
37
|
-
2. An [Authorizer](#authorizer) concern which provides a
|
38
|
-
|
39
|
-
resources in relation to an already authenticated resource, like a
|
40
|
-
User or Admin.
|
25
|
+
1. A [Roleable](#roleable) concern which provides a way to define access roles
|
26
|
+
on User models or on join through models.
|
27
|
+
2. An [Authorizer](#authorizer) concern which provides a declarative API
|
28
|
+
to controllers for finding and authorizing model resources.
|
41
29
|
|
42
30
|
#### Roleable
|
43
31
|
|
44
|
-
First, we need to define some roles.
|
45
|
-
on either "User" models or on relationship models (such as a through
|
46
|
-
model linking a User to another resource). Roles are stored as an
|
32
|
+
First, we need to define some roles on a model. Roles are stored as an
|
47
33
|
integer and [bitmasking](https://en.wikipedia.org/wiki/Mask_(computing))
|
48
|
-
is used to determine
|
49
|
-
|
34
|
+
is used to determine the roles assigned for that model. SimonSays
|
35
|
+
provides a generator for creating a new migration for this required
|
36
|
+
attribute:
|
37
|
+
|
38
|
+
```bash
|
39
|
+
rails g active_record:simon_says User
|
40
|
+
```
|
50
41
|
|
51
|
-
For example:
|
42
|
+
Now we can define some roles in our User model. For example:
|
52
43
|
|
53
44
|
```ruby
|
54
45
|
class User < ActiveRecord::Base
|
@@ -87,12 +78,19 @@ end
|
|
87
78
|
# => [:support]
|
88
79
|
```
|
89
80
|
|
81
|
+
Make sure to generate a migration using the correct attribute name if
|
82
|
+
`:as` is used. For example:
|
83
|
+
|
84
|
+
```bash
|
85
|
+
rails g active_record:simon_says Admin access
|
86
|
+
```
|
87
|
+
|
90
88
|
We can also use `has_roles` to define roles on a join through model
|
91
89
|
which is used to associate a User with a resource.
|
92
90
|
|
93
91
|
```ruby
|
94
92
|
|
95
|
-
class
|
93
|
+
class Permission < ActiveRecord::Base
|
96
94
|
include SimonSays::Roleable
|
97
95
|
|
98
96
|
belongs_to :user
|
@@ -101,20 +99,20 @@ class Membership < ActiveRecord::Base
|
|
101
99
|
has_roles :download, :edit, :delete,
|
102
100
|
end
|
103
101
|
|
104
|
-
# >
|
102
|
+
# > Permission.new(roles: Permission::ROLES).roles
|
105
103
|
# => [:download, :edit, :delete]
|
106
104
|
```
|
107
105
|
|
108
106
|
It is useful to note the dynamically generated `has_` methods as shown
|
109
107
|
in the User model as well the `ROLES` constant which is used in the
|
110
|
-
|
111
|
-
code](https://github.com/SimplyBuilt/SimonSays/blob/master/lib/simon_says/roleable.rb)
|
112
|
-
to see how features are dynamically generated
|
108
|
+
Permission example. Take a look at the `Roleable`
|
109
|
+
[source code](https://github.com/SimplyBuilt/SimonSays/blob/master/lib/simon_says/roleable.rb)
|
110
|
+
to see how features are dynamically generated with `has_roles`.
|
113
111
|
|
114
112
|
#### Authorizer
|
115
113
|
|
116
114
|
The `Authorizer` concern provides several methods that can be used within
|
117
|
-
your controllers in declarative manner.
|
115
|
+
your controllers in a declarative manner.
|
118
116
|
|
119
117
|
Please note, certain assumptions are made with `Authorizer`. Building
|
120
118
|
upon the above User and Admin model examples, `Authorizer` would assume
|
@@ -138,7 +136,8 @@ to be used in controllers. All of these methods accept the `:only` and
|
|
138
136
|
- `authorize_resource(resource, *roles)`: Authorize resource for given
|
139
137
|
roles
|
140
138
|
- `find_and_authorize(resource, *roles)`: Find a resource and then try
|
141
|
-
authorize it for the given roles
|
139
|
+
authorize it for the given roles. If successful, the resource is
|
140
|
+
assigned to an instance variable
|
142
141
|
|
143
142
|
When find resources, the `default_authorization_scope` is used. It can
|
144
143
|
be customized on a per-controller basis. For example:
|
@@ -154,22 +153,23 @@ end
|
|
154
153
|
To authorize resources against a given role, we use either `authorize`
|
155
154
|
or `find_and_authorize`. For example, consider this
|
156
155
|
`DocumentsController` which uses an authenticated `User` resource and a
|
157
|
-
`
|
156
|
+
`Permission` through model:
|
158
157
|
|
159
158
|
```ruby
|
160
159
|
class DocumentsController < ApplicationController
|
161
160
|
authenticate :user
|
162
161
|
|
163
|
-
find_and_authorize :
|
164
|
-
find_and_authorize :
|
162
|
+
find_and_authorize :document, :edit, through: :permissions, only: [:edit, :update]
|
163
|
+
find_and_authorize :document, :delete, through: :permissions, only: :destroy
|
165
164
|
end
|
166
165
|
```
|
167
166
|
|
168
|
-
This controller will find Document
|
167
|
+
This controller will find a Document resource and assign it to the
|
169
168
|
`@document` instance variable. For the `:edit` and `:update` actions,
|
170
|
-
it'll require
|
171
|
-
|
172
|
-
|
169
|
+
it'll require a permission with an `:edit` role. For the `:destroy`
|
170
|
+
method, a permission with the `:delete` role is required. Since the
|
171
|
+
`:through` option is used, a `@permission` instance variable will also
|
172
|
+
be created.
|
173
173
|
|
174
174
|
The `find_resource` method may raise an `ActiveRecord::RecordNotFound`
|
175
175
|
exception. The `authorize` method may raise a
|
@@ -177,7 +177,7 @@ exception. The `authorize` method may raise a
|
|
177
177
|
access. As a result, the `find_and_authorize` method may raise either
|
178
178
|
exception.
|
179
179
|
|
180
|
-
We can also use a different authorization scope
|
180
|
+
We can also use a different authorization scope with the `:from`
|
181
181
|
option for `find_resource` and `find_and_authorize`. For example:
|
182
182
|
|
183
183
|
```ruby
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rails/generators/active_record'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Generators
|
5
|
+
class SimonSaysGenerator < ActiveRecord::Generators::Base
|
6
|
+
source_root File.expand_path("../templates", __FILE__)
|
7
|
+
|
8
|
+
def copy_simon_says_migration
|
9
|
+
migration_template "migration.rb", "db/migrate/simon_says_add_to_#{table_name}.rb"
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def migration_version
|
15
|
+
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]" if Rails.version >= '5.0.0'
|
16
|
+
end
|
17
|
+
|
18
|
+
def role_attribute_name
|
19
|
+
args.first || 'roles'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -151,11 +151,6 @@ module SimonSays
|
|
151
151
|
name = options[:resource]
|
152
152
|
end
|
153
153
|
|
154
|
-
attr = Roleable.registry[name]
|
155
|
-
|
156
|
-
required ||= options[attr.to_sym]
|
157
|
-
required = [required] unless Array === required
|
158
|
-
|
159
154
|
record = instance_variable_get("@#{name}")
|
160
155
|
|
161
156
|
if record.nil? # must be devise scope
|
@@ -163,12 +158,16 @@ module SimonSays
|
|
163
158
|
send "authenticate_#{name}!"
|
164
159
|
end
|
165
160
|
|
166
|
-
|
161
|
+
role_attr = record.class.role_attribute_name
|
162
|
+
actual = record.send(role_attr)
|
163
|
+
|
164
|
+
required ||= options[role_attr]
|
165
|
+
required = [required] unless Array === required
|
167
166
|
|
168
167
|
# actual roles must have at least
|
169
168
|
# one required role (array intersection)
|
170
169
|
((required & actual).size > 0).tap do |res|
|
171
|
-
raise Denied.new(
|
170
|
+
raise Denied.new(role_attr, required, actual) unless res
|
172
171
|
end
|
173
172
|
end
|
174
173
|
|
@@ -187,7 +186,6 @@ module SimonSays
|
|
187
186
|
|
188
187
|
else
|
189
188
|
klass = (options[:class_name] || resource).to_s
|
190
|
-
# TODO support array of namespaces?
|
191
189
|
klass = "#{options[:namespace]}/#{klass}" if options[:namespace]
|
192
190
|
|
193
191
|
scope = klass.classify.constantize
|
data/lib/simon_says/roleable.rb
CHANGED
@@ -2,11 +2,6 @@ module SimonSays
|
|
2
2
|
module Roleable
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
|
-
def self.registry # :nodoc:
|
6
|
-
# "global" registry we'll use when authorizing
|
7
|
-
@registry ||= {}
|
8
|
-
end
|
9
|
-
|
10
5
|
module ClassMethods
|
11
6
|
# Provides a declarative method to introduce role based
|
12
7
|
# access controller through a give integer mask.
|
@@ -65,8 +60,6 @@ module SimonSays
|
|
65
60
|
singular = name.singularize
|
66
61
|
const = name.upcase
|
67
62
|
|
68
|
-
Roleable.registry[model_name.to_s.downcase.to_sym] ||= name
|
69
|
-
|
70
63
|
roles.map!(&:to_sym)
|
71
64
|
|
72
65
|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__
|
@@ -88,6 +81,10 @@ module SimonSays
|
|
88
81
|
def has_#{name}?(*args)
|
89
82
|
(#{name} & args).size > 0
|
90
83
|
end
|
84
|
+
|
85
|
+
def self.role_attribute_name
|
86
|
+
:#{name}
|
87
|
+
end
|
91
88
|
RUBY_EVAL
|
92
89
|
|
93
90
|
if name != singular
|
data/lib/simon_says/version.rb
CHANGED
data/simon_says.gemspec
CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
|
23
23
|
spec.add_development_dependency "bundler", "~> 1.9"
|
24
24
|
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
-
spec.add_development_dependency "rails", ">= 4.0", "< 5.
|
25
|
+
spec.add_development_dependency "rails", ">= 4.0", "< 5.2"
|
26
26
|
spec.add_development_dependency "responders", "~> 2.0"
|
27
27
|
spec.add_development_dependency "mocha", "~> 1.1"
|
28
28
|
end
|
@@ -9,13 +9,13 @@ class DocumentsController < ApplicationController
|
|
9
9
|
find_and_authorize :document, :download, through: :memberships, only: :send_file
|
10
10
|
|
11
11
|
def index
|
12
|
-
@documents =
|
12
|
+
@documents = current_user.documents
|
13
13
|
|
14
14
|
respond_with @documents
|
15
15
|
end
|
16
16
|
|
17
17
|
def create
|
18
|
-
@document =
|
18
|
+
@document = current_user.documents.create(document_params)
|
19
19
|
|
20
20
|
respond_with @document
|
21
21
|
end
|
data/test/rails_app/db/schema.rb
CHANGED
@@ -13,35 +13,35 @@
|
|
13
13
|
ActiveRecord::Schema.define(version: 20160823220959) do
|
14
14
|
|
15
15
|
create_table "admin_reports", force: :cascade do |t|
|
16
|
-
t.string
|
16
|
+
t.string "title"
|
17
17
|
t.datetime "created_at", null: false
|
18
18
|
t.datetime "updated_at", null: false
|
19
19
|
end
|
20
20
|
|
21
21
|
create_table "admins", force: :cascade do |t|
|
22
|
-
t.integer
|
23
|
-
t.datetime "created_at",
|
24
|
-
t.datetime "updated_at",
|
22
|
+
t.integer "access_mask"
|
23
|
+
t.datetime "created_at", null: false
|
24
|
+
t.datetime "updated_at", null: false
|
25
25
|
end
|
26
26
|
|
27
27
|
create_table "documents", force: :cascade do |t|
|
28
|
-
t.string
|
28
|
+
t.string "title"
|
29
29
|
t.datetime "created_at", null: false
|
30
30
|
t.datetime "updated_at", null: false
|
31
31
|
end
|
32
32
|
|
33
33
|
create_table "images", force: :cascade do |t|
|
34
|
-
t.string
|
34
|
+
t.string "token"
|
35
35
|
t.datetime "created_at", null: false
|
36
36
|
t.datetime "updated_at", null: false
|
37
37
|
end
|
38
38
|
|
39
39
|
create_table "memberships", force: :cascade do |t|
|
40
|
-
t.integer
|
41
|
-
t.integer
|
42
|
-
t.integer
|
43
|
-
t.datetime "created_at",
|
44
|
-
t.datetime "updated_at",
|
40
|
+
t.integer "user_id"
|
41
|
+
t.integer "document_id"
|
42
|
+
t.integer "roles_mask", default: 0
|
43
|
+
t.datetime "created_at", null: false
|
44
|
+
t.datetime "updated_at", null: false
|
45
45
|
t.index ["document_id"], name: "index_memberships_on_document_id"
|
46
46
|
t.index ["user_id"], name: "index_memberships_on_user_id"
|
47
47
|
end
|
@@ -144,11 +144,11 @@ class RoleableTest < ActiveSupport::TestCase
|
|
144
144
|
]
|
145
145
|
end
|
146
146
|
|
147
|
-
test "Membership
|
148
|
-
|
147
|
+
test "Membership defines role_attribute_name" do
|
148
|
+
assert_equal :roles, Membership.role_attribute_name
|
149
149
|
end
|
150
150
|
|
151
|
-
test "Admin
|
152
|
-
|
151
|
+
test "Admin defines role_attribute_name" do
|
152
|
+
assert_equal :access, Admin.role_attribute_name
|
153
153
|
end
|
154
154
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simon_says
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Coyne
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2017-
|
13
|
+
date: 2017-10-07 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
@@ -63,7 +63,7 @@ dependencies:
|
|
63
63
|
version: '4.0'
|
64
64
|
- - "<"
|
65
65
|
- !ruby/object:Gem::Version
|
66
|
-
version: '5.
|
66
|
+
version: '5.2'
|
67
67
|
type: :development
|
68
68
|
prerelease: false
|
69
69
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -73,7 +73,7 @@ dependencies:
|
|
73
73
|
version: '4.0'
|
74
74
|
- - "<"
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
version: '5.
|
76
|
+
version: '5.2'
|
77
77
|
- !ruby/object:Gem::Dependency
|
78
78
|
name: responders
|
79
79
|
requirement: !ruby/object:Gem::Requirement
|
@@ -120,6 +120,8 @@ files:
|
|
120
120
|
- README.md
|
121
121
|
- Rakefile
|
122
122
|
- SimonSays.png
|
123
|
+
- lib/generators/active_record/simon_says_generator.rb
|
124
|
+
- lib/generators/active_record/templates/migration.rb
|
123
125
|
- lib/simon_says.rb
|
124
126
|
- lib/simon_says/authorizer.rb
|
125
127
|
- lib/simon_says/roleable.rb
|
@@ -234,7 +236,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
234
236
|
version: '0'
|
235
237
|
requirements: []
|
236
238
|
rubyforge_project:
|
237
|
-
rubygems_version: 2.6.
|
239
|
+
rubygems_version: 2.6.13
|
238
240
|
signing_key:
|
239
241
|
specification_version: 4
|
240
242
|
summary: Light-weight, declarative authorization and access control for Rails
|