ez-permissions 0.1.0
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/MIT-LICENSE +20 -0
- data/README.md +210 -0
- data/Rakefile +30 -0
- data/app/controllers/ez/permissions/application_controller.rb +10 -0
- data/app/models/ez/permissions/application_record.rb +9 -0
- data/app/models/ez/permissions/model.rb +25 -0
- data/app/models/ez/permissions/model_role.rb +13 -0
- data/app/models/ez/permissions/permission.rb +12 -0
- data/app/models/ez/permissions/permission_role.rb +12 -0
- data/app/models/ez/permissions/role.rb +14 -0
- data/config/routes.rb +5 -0
- data/lib/ez/permissions/api/authorize.rb +56 -0
- data/lib/ez/permissions/api/models.rb +29 -0
- data/lib/ez/permissions/api/permissions.rb +54 -0
- data/lib/ez/permissions/api/roles.rb +37 -0
- data/lib/ez/permissions/api.rb +17 -0
- data/lib/ez/permissions/dsl.rb +72 -0
- data/lib/ez/permissions/engine.rb +9 -0
- data/lib/ez/permissions/railtie.rb +9 -0
- data/lib/ez/permissions/resource.rb +29 -0
- data/lib/ez/permissions/version.rb +7 -0
- data/lib/ez/permissions.rb +19 -0
- data/lib/generators/ez/permissions/install_generator.rb +30 -0
- data/lib/generators/ez/permissions/migrations_generator.rb +73 -0
- data/lib/tasks/ez/permissions_tasks.rake +6 -0
- metadata +210 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 63fcfa633d299ba98948f0139ad49873bbdd3098f45a6e714d2fb5da27f28b18
|
4
|
+
data.tar.gz: c392ff7f90575129d0c7c7a99d873c7552acf88d59b82460e766a3c42dac4461
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e6bb7cfe77c7b5f3c51d38d5e4f9349d2e59c7d8e47de0f9c728c66190453013fef6ce348cfef4bfcba40cda3d58296d1b7a047a0001c1358a1274b01cbc152b
|
7
|
+
data.tar.gz: 37c0fe0b3dd8f727b20f8a4820cdd56c95e29dd413b0317c8f0cee204fe9e152129805a96f4a402e90255571b1b9ce9c0d8610af5057d07a31eb635d9806bd52
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2019 Volodymyr Sveredyuk
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
# Ez::Permissions
|
2
|
+
|
3
|
+
[](https://travis-ci.org/ez-engines/ez-permissions)
|
4
|
+
|
5
|
+
**Ez Permissions** (read as "easy permissions") - one of the [ez-engines](https://github.com/ez-engines) collection that helps easily add permissions interface to your [Rails](http://rubyonrails.org/) application.
|
6
|
+
|
7
|
+
- Most advanced RBAC model:
|
8
|
+
- Flexible tool with simple DSL and confguration
|
9
|
+
- All in one solution
|
10
|
+
- Convetion over configuration principles.
|
11
|
+
- Depends on [ez-core](https://github.com/ez-engines/ez-core)
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
```ruby
|
16
|
+
gem 'ez-permissions'
|
17
|
+
```
|
18
|
+
|
19
|
+
## Generators
|
20
|
+
|
21
|
+
Generate configuration file:
|
22
|
+
```bash
|
23
|
+
rails generate ez:permissions:install
|
24
|
+
```
|
25
|
+
|
26
|
+
### Configuration
|
27
|
+
|
28
|
+
Configuration interface allows you to change default behavior
|
29
|
+
```ruby
|
30
|
+
Ez::Permissions.configure do |config|
|
31
|
+
# If in generated migrations you changed table names, please configure them here:
|
32
|
+
config.permission_table_name = 'my_permissions'
|
33
|
+
config.roles_table_name = 'my_roles'
|
34
|
+
config.models_roles_table_name = 'my_model_roles'
|
35
|
+
config.permissions_roles_table_name = 'my_permissions_roles'
|
36
|
+
|
37
|
+
# Define your custom callbacks
|
38
|
+
config.handle_no_permission_model = lambda { |context|
|
39
|
+
raise 'User not exist'
|
40
|
+
}
|
41
|
+
|
42
|
+
config.handle_not_authorized = lambda { |context|
|
43
|
+
raise 'Not authorized'
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
```
|
48
|
+
### ActiveRecord migrations:
|
49
|
+
|
50
|
+
**If you need change table names, please change configuration first**
|
51
|
+
|
52
|
+
And run
|
53
|
+
```bash
|
54
|
+
rails generate ez:permissions:migrations
|
55
|
+
```
|
56
|
+
|
57
|
+
## DSL
|
58
|
+
|
59
|
+
Simple DSL for difinition of permission relationships
|
60
|
+
```ruby
|
61
|
+
Ez::Permissions::DSL.define do |setup|
|
62
|
+
# You need add all resources of your application and possible actions
|
63
|
+
setup.add :roles, actions: %i[create read]
|
64
|
+
|
65
|
+
# Use `crud` for adding default `create`, `read`, `update` and `delete` actions
|
66
|
+
# And any your custom action
|
67
|
+
setup.add :permissions, actions: %i[crud my_custom_action]
|
68
|
+
|
69
|
+
# Actions option are not required. In such case you add all crud actions by default
|
70
|
+
setup.add :users
|
71
|
+
setup.add :projects
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
## Permission model
|
76
|
+
|
77
|
+
In your application, you usually have `User` model.
|
78
|
+
```ruby
|
79
|
+
class User < ActiveRecord::Base
|
80
|
+
include Ez::Permissions::Model
|
81
|
+
end
|
82
|
+
|
83
|
+
user = User.first
|
84
|
+
|
85
|
+
# User model become permission model
|
86
|
+
user.roles #=> [application level roles]
|
87
|
+
user.assigned_roles #=> [user owned roles, gloabal and scoped]
|
88
|
+
user.permissions #=> [user available permissions through assigned_roles]
|
89
|
+
```
|
90
|
+
|
91
|
+
## API
|
92
|
+
|
93
|
+
**Please, do not use direct rails code like:** `Ez::Permissions::Permission.create(name: 'admin')`
|
94
|
+
|
95
|
+
Instead you should use public api. You can extend you custom module with `API` mixin
|
96
|
+
```ruby
|
97
|
+
# Use engine facade methods
|
98
|
+
Ez::Permissions::API
|
99
|
+
|
100
|
+
# or extend your own module and keep your code clean
|
101
|
+
module Permissions
|
102
|
+
extend Ez::Permissions::API::Roles
|
103
|
+
extend Ez::Permissions::API::Permissions
|
104
|
+
extend Ez::Permissions::API::Models
|
105
|
+
extend Ez::Permissions::API::Authorize
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
### Roles
|
110
|
+
```ruby
|
111
|
+
# Create regular role
|
112
|
+
Permissions.create_role(:user)
|
113
|
+
Permissions.create_role(:admin)
|
114
|
+
|
115
|
+
# Get role object by name
|
116
|
+
Permissions.get_role(:user)
|
117
|
+
|
118
|
+
# Update role attributes
|
119
|
+
Permissions.update_role(:user, name: 'super_user')
|
120
|
+
|
121
|
+
# Delete role
|
122
|
+
Permissions.delete_role(:user)
|
123
|
+
|
124
|
+
# Assign role to the user
|
125
|
+
user = User.first
|
126
|
+
Permissions.assign_role(user, :admin)
|
127
|
+
|
128
|
+
# Assign role to the user in scope of any resource
|
129
|
+
project = Project.first
|
130
|
+
Permissions.assign_role(user, :admin, scoped: project)
|
131
|
+
|
132
|
+
# Reject user role in global scope, but project admin role will stay
|
133
|
+
Permissions.reject_role(user, :admin)
|
134
|
+
```
|
135
|
+
|
136
|
+
### Permissions
|
137
|
+
```ruby
|
138
|
+
# Create a role
|
139
|
+
Permissions.create_role(:user)
|
140
|
+
|
141
|
+
# Grant role's possibility to have action per resource
|
142
|
+
Permissions.grant_permission(:user, :read, :projects)
|
143
|
+
|
144
|
+
# Grant all defined actions per resource
|
145
|
+
Permissions.grant_permission(:user, :all, :projects)
|
146
|
+
|
147
|
+
# Revoke particular permission
|
148
|
+
Permissions.revoke_permission(:user, :create, :projects)
|
149
|
+
```
|
150
|
+
|
151
|
+
### Authorize access
|
152
|
+
```ruby
|
153
|
+
user = User.first
|
154
|
+
project = Project.first
|
155
|
+
|
156
|
+
Permissions.create_role(:admin)
|
157
|
+
Permissions.grant_permission(:admin, :all, :users)
|
158
|
+
Permissions.assign_role(user, :admin, scoped: project)
|
159
|
+
|
160
|
+
Permissions.authorize!(user, :create, :users, scoped: project) do
|
161
|
+
# code here would be executed if user has permissions
|
162
|
+
# for user creation in particular project
|
163
|
+
end
|
164
|
+
|
165
|
+
# otherwise catch exception
|
166
|
+
Ez::Permissions::API::Authrozation::NotAuthorized
|
167
|
+
|
168
|
+
# if you don't want raise exception, just use
|
169
|
+
Permissions.authorize(user, :create, :users) { puts 'Yeahh!' } #=> false
|
170
|
+
# Because user has scoped role in the project and don't has global role.
|
171
|
+
```
|
172
|
+
|
173
|
+
### Kepp it excplicit!
|
174
|
+
You can wonder, why we just not add authorization methods to user instance, like:
|
175
|
+
```ruby
|
176
|
+
user.can?(:something)
|
177
|
+
```
|
178
|
+
Because ez-permissions engine don't want pollute your application and keep implementation isolated in external modules.
|
179
|
+
Of course, you can use them as mixins, but it's up to you.
|
180
|
+
|
181
|
+
## Understanding scoped roles
|
182
|
+
- System have many roles
|
183
|
+
- User has many assigned roles
|
184
|
+
- User can has role in scope of some resource (Project, Company, Business, etc.)
|
185
|
+
- User can has role in global scope (without scope)
|
186
|
+
- If user want access data in scope of resource - user must has assigned role scoped for this resource
|
187
|
+
- If user want access data in global scope - user must has assigned role wihtout any scoped resorce (global role)
|
188
|
+
- User with global role - can't access scoped resources.
|
189
|
+
- User with scoped role - can't access global resources.
|
190
|
+
|
191
|
+
## TODO
|
192
|
+
- [x] Add README
|
193
|
+
- [x] Add Role model
|
194
|
+
- [x] Add Permissions model
|
195
|
+
- [x] Add PermissionsRole model
|
196
|
+
- [x] Add rails generators for migrations
|
197
|
+
- [x] Add rails generators for configuration
|
198
|
+
- [x] Add configuration DSL
|
199
|
+
- [x] Add Permissions API for managing relationships
|
200
|
+
- [x] User can has multiple roles
|
201
|
+
- [x] Better errors for non-existing records
|
202
|
+
- [x] Add permissions helpers `authorize` and `authorize!`
|
203
|
+
- [x] Move all erros under `Ez::Permissions::API` namespace and add `Error` suffix
|
204
|
+
- [ ] Add helper methods for seed grant permissions
|
205
|
+
|
206
|
+
## Contributing
|
207
|
+
Contribution directions go here.
|
208
|
+
|
209
|
+
## License
|
210
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'bundler/setup'
|
5
|
+
rescue LoadError
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rdoc/task'
|
10
|
+
|
11
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
12
|
+
rdoc.rdoc_dir = 'rdoc'
|
13
|
+
rdoc.title = 'Ez::Permissions'
|
14
|
+
rdoc.options << '--line-numbers'
|
15
|
+
rdoc.rdoc_files.include('README.md')
|
16
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
17
|
+
end
|
18
|
+
|
19
|
+
APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
|
20
|
+
load 'rails/tasks/engine.rake'
|
21
|
+
|
22
|
+
load 'rails/tasks/statistics.rake'
|
23
|
+
|
24
|
+
require 'bundler/gem_tasks'
|
25
|
+
|
26
|
+
require 'rspec/core/rake_task'
|
27
|
+
|
28
|
+
RSpec::Core::RakeTask.new(:spec)
|
29
|
+
|
30
|
+
task default: :spec
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ez
|
4
|
+
module Permissions
|
5
|
+
module Model
|
6
|
+
# rubocop:disable Metrics/MethodLength
|
7
|
+
def self.included(base)
|
8
|
+
base.has_many :assigned_roles,
|
9
|
+
class_name: 'Ez::Permissions::ModelRole',
|
10
|
+
as: :model
|
11
|
+
|
12
|
+
base.has_many :roles,
|
13
|
+
-> { distinct },
|
14
|
+
through: :assigned_roles,
|
15
|
+
class_name: 'Ez::Permissions::Role'
|
16
|
+
|
17
|
+
base.has_many :permissions,
|
18
|
+
-> { distinct },
|
19
|
+
through: :roles,
|
20
|
+
class_name: 'Ez::Permissions::Permission'
|
21
|
+
end
|
22
|
+
# rubocop:enable Metrics/MethodLength
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ez
|
4
|
+
module Permissions
|
5
|
+
class ModelRole < ApplicationRecord
|
6
|
+
self.table_name = Ez::Permissions.config.models_roles_table_name
|
7
|
+
|
8
|
+
belongs_to :model, polymorphic: true
|
9
|
+
belongs_to :scoped, polymorphic: true, optional: true
|
10
|
+
belongs_to :role
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ez
|
4
|
+
module Permissions
|
5
|
+
class Permission < ApplicationRecord
|
6
|
+
self.table_name = Ez::Permissions.config.permissions_table_name
|
7
|
+
|
8
|
+
validates :resource, presence: true
|
9
|
+
validates :action, presence: true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ez
|
4
|
+
module Permissions
|
5
|
+
class PermissionRole < ApplicationRecord
|
6
|
+
self.table_name = Ez::Permissions.config.permissions_roles_table_name
|
7
|
+
|
8
|
+
belongs_to :permission, class_name: 'Ez::Permissions::Permission'
|
9
|
+
belongs_to :role, class_name: 'Ez::Permissions::Role'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ez
|
4
|
+
module Permissions
|
5
|
+
class Role < ApplicationRecord
|
6
|
+
self.table_name = Ez::Permissions.config.roles_table_name
|
7
|
+
|
8
|
+
has_and_belongs_to_many :permissions
|
9
|
+
|
10
|
+
validates :name, presence: true
|
11
|
+
validates :name, uniqueness: true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ez
|
4
|
+
module Permissions
|
5
|
+
module API
|
6
|
+
module Authorize
|
7
|
+
NotAuthorized = Class.new(StandardError)
|
8
|
+
|
9
|
+
def authorize!(model, *actions, resource, scoped: nil, &block)
|
10
|
+
authorize(model, *actions, resource, scoped: scoped, raise_exception: true, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def authorize(model, *actions, resource, scoped: nil, raise_exception: false)
|
14
|
+
return handle_no_permission_model_callback.call(self) if handle_no_permission_model_callback && !model
|
15
|
+
|
16
|
+
return yield if permissions(model, *actions, resource, scoped: scoped).any?
|
17
|
+
|
18
|
+
return handle_not_authorized_callback.call(self) if handle_not_authorized_callback
|
19
|
+
|
20
|
+
raise NotAuthorized, not_authorized_msg(model, actions, resource, scoped) if raise_exception
|
21
|
+
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def permissions(model, *actions, resource, scoped: nil)
|
28
|
+
# TODO: Refactor to 1 query with joins
|
29
|
+
roles_ids = model.assigned_roles.where(scoped: scoped).pluck(:role_id)
|
30
|
+
permission_ids = Ez::Permissions::PermissionRole.where(role_id: roles_ids).pluck(:permission_id)
|
31
|
+
|
32
|
+
Ez::Permissions::Permission.where(
|
33
|
+
id: permission_ids,
|
34
|
+
resource: resource,
|
35
|
+
action: actions.map(&:to_s)
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
def not_authorized_msg(model, actions, resource, scoped = nil)
|
40
|
+
msg = "#{model.class}##{model.id} is not authorized to [#{actions.join(', ')} -> #{resource}]"
|
41
|
+
msg = "#{msg} for #{scoped.class}##{scoped.id}" if scoped
|
42
|
+
|
43
|
+
msg
|
44
|
+
end
|
45
|
+
|
46
|
+
def handle_no_permission_model_callback
|
47
|
+
Ez::Permissions.config.handle_no_permission_model
|
48
|
+
end
|
49
|
+
|
50
|
+
def handle_not_authorized_callback
|
51
|
+
Ez::Permissions.config.handle_not_authorized
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ez
|
4
|
+
module Permissions
|
5
|
+
module API
|
6
|
+
module Models
|
7
|
+
def assign_role(model, role_name, scoped: nil)
|
8
|
+
role = Ez::Permissions::API.get_role!(role_name)
|
9
|
+
|
10
|
+
Ez::Permissions::ModelRole.find_or_create_by!(
|
11
|
+
role: role,
|
12
|
+
model: model,
|
13
|
+
scoped: scoped
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def reject_role(model, role_name, scoped: nil)
|
18
|
+
role = Ez::Permissions::API.get_role!(role_name)
|
19
|
+
|
20
|
+
Ez::Permissions::ModelRole.find_by(
|
21
|
+
role: role,
|
22
|
+
model: model,
|
23
|
+
scoped: scoped
|
24
|
+
)&.delete
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ez
|
4
|
+
module Permissions
|
5
|
+
module API
|
6
|
+
module Permissions
|
7
|
+
PermissionNotFound = Class.new(StandardError)
|
8
|
+
|
9
|
+
def get_permission!(action, resource)
|
10
|
+
Ez::Permissions::Permission.find_by!(resource: resource, action: action)
|
11
|
+
rescue ActiveRecord::RecordNotFound
|
12
|
+
raise PermissionNotFound, "Permission [#{action} -> #{resource}] not found"
|
13
|
+
end
|
14
|
+
|
15
|
+
def grant_permission(role_name, action, resource)
|
16
|
+
role = Ez::Permissions::API.get_role!(role_name)
|
17
|
+
|
18
|
+
if action == :all
|
19
|
+
grant_all_permissions(role, resource)
|
20
|
+
else
|
21
|
+
permission = get_permission!(action, resource)
|
22
|
+
grant_single_permission(role, permission)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def revoke_permission(role_name, action, resource)
|
27
|
+
role = Ez::Permissions::API.get_role!(role_name)
|
28
|
+
permission = get_permission!(action, resource)
|
29
|
+
|
30
|
+
Ez::Permissions::PermissionRole.find_by(
|
31
|
+
role: role,
|
32
|
+
permission: permission
|
33
|
+
)&.delete
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def grant_single_permission(role, permission)
|
39
|
+
Ez::Permissions::PermissionRole.find_or_create_by!(
|
40
|
+
role: role,
|
41
|
+
permission: permission
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
def grant_all_permissions(role, resource)
|
46
|
+
Ez::Permissions::DSL.resource(resource).actions.each do |action|
|
47
|
+
permission = get_permission!(action, resource)
|
48
|
+
grant_single_permission(role, permission)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ez
|
4
|
+
module Permissions
|
5
|
+
module API
|
6
|
+
module Roles
|
7
|
+
RoleNotFound = Class.new(StandardError)
|
8
|
+
|
9
|
+
def create_role(name)
|
10
|
+
Role.create(name: name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_role(name)
|
14
|
+
Role.find_by(name: name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_role!(name)
|
18
|
+
Role.find_by!(name: name)
|
19
|
+
rescue ActiveRecord::RecordNotFound
|
20
|
+
raise RoleNotFound, "Role #{name} not found"
|
21
|
+
end
|
22
|
+
|
23
|
+
def update_role(role_name, name:)
|
24
|
+
role = get_role!(role_name)
|
25
|
+
|
26
|
+
role.update(name: name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete_role(name)
|
30
|
+
role = get_role!(name)
|
31
|
+
|
32
|
+
role.delete
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'api/roles'
|
4
|
+
require_relative 'api/permissions'
|
5
|
+
require_relative 'api/models'
|
6
|
+
require_relative 'api/authorize'
|
7
|
+
|
8
|
+
module Ez
|
9
|
+
module Permissions
|
10
|
+
module API
|
11
|
+
extend Ez::Permissions::API::Roles
|
12
|
+
extend Ez::Permissions::API::Permissions
|
13
|
+
extend Ez::Permissions::API::Models
|
14
|
+
extend Ez::Permissions::API::Authorize
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'resource'
|
4
|
+
|
5
|
+
module Ez
|
6
|
+
module Permissions
|
7
|
+
class DSL
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def self.define
|
11
|
+
yield DSL.instance
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.resources
|
15
|
+
DSL.instance.resources
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.resource(name)
|
19
|
+
DSL.instance.resources.find { |r| r.name.to_sym == name.to_sym }
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :resources
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@resources = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def add(name, options = {})
|
29
|
+
if self.class.resource(name)
|
30
|
+
return message("[WARN] Ez::Permissions resource [#{name}] has been already defined!")
|
31
|
+
end
|
32
|
+
|
33
|
+
resource = Ez::Permissions::Resource.new(name, options)
|
34
|
+
|
35
|
+
message(
|
36
|
+
"[SUCCESS] Ez::Permissions resource [#{name}] has been successfully registred with actions: \
|
37
|
+
[#{resource.actions.join(', ')}]"
|
38
|
+
)
|
39
|
+
|
40
|
+
@resources << resource
|
41
|
+
seed_to_db resource
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def message(txt)
|
47
|
+
STDOUT.puts(txt)
|
48
|
+
end
|
49
|
+
|
50
|
+
def seed_to_db(resource)
|
51
|
+
try_db_connection
|
52
|
+
|
53
|
+
return unless ActiveRecord::Base.connection.data_source_exists?(Ez::Permissions.config.permissions_table_name)
|
54
|
+
|
55
|
+
return unless resource.actions
|
56
|
+
|
57
|
+
resource.actions.each do |action|
|
58
|
+
Ez::Permissions::Permission.where(
|
59
|
+
resource: resource.name,
|
60
|
+
action: action
|
61
|
+
).first_or_create!
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def try_db_connection
|
66
|
+
ActiveRecord::Base.connection
|
67
|
+
rescue ActiveRecord::NoDatabaseError
|
68
|
+
STDOUT.puts 'Database does not exist'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ez
|
4
|
+
module Permissions
|
5
|
+
class Resource
|
6
|
+
ACTIONS = %i[create read update delete].freeze
|
7
|
+
|
8
|
+
attr_reader :name, :model, :actions
|
9
|
+
|
10
|
+
def initialize(name, options = {})
|
11
|
+
@name = name
|
12
|
+
@model = options.fetch(:model, nil)
|
13
|
+
@actions = process_actions(options.fetch(:actions, nil))
|
14
|
+
end
|
15
|
+
|
16
|
+
def <=>(other)
|
17
|
+
name <=> other.name
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def process_actions(actions)
|
23
|
+
return ACTIONS unless actions
|
24
|
+
|
25
|
+
actions.map { |action| action == :crud ? ACTIONS : action }.flatten
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ez/permissions/engine'
|
4
|
+
require 'ez/permissions/dsl'
|
5
|
+
require 'ez/permissions/api'
|
6
|
+
require 'ez/configurator'
|
7
|
+
|
8
|
+
module Ez
|
9
|
+
module Permissions
|
10
|
+
include Ez::Configurator
|
11
|
+
|
12
|
+
configure do |config|
|
13
|
+
config.permissions_table_name = 'ez_permissions_permissions'
|
14
|
+
config.roles_table_name = 'ez_permissions_roles'
|
15
|
+
config.models_roles_table_name = 'ez_permissions_model_roles'
|
16
|
+
config.permissions_roles_table_name = 'ez_permissions_permissions_roles'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ez
|
4
|
+
module Permissions
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
def create_migration
|
7
|
+
create_file 'config/initializers/ez_permissions.rb',
|
8
|
+
"# frozen_string_literal: true
|
9
|
+
|
10
|
+
Ez::Permissions.configure do |config|
|
11
|
+
# config.permission_table_name = 'ez_permissions_permissions'
|
12
|
+
# config.roles_table_name = 'ez_permissions_roles'
|
13
|
+
# config.models_roles_table_name = 'ez_permissions_model_roles'
|
14
|
+
# config.permissions_roles_table_name = 'ez_permissions_permissions_roles'
|
15
|
+
|
16
|
+
# config.handle_no_permission_model = lambda { |context|
|
17
|
+
# here you can define your custom callback
|
18
|
+
# in case when permission model (user) is nil
|
19
|
+
# }
|
20
|
+
|
21
|
+
# config.handle_not_authorized = lambda { |context|
|
22
|
+
# here you can define your custom callback
|
23
|
+
# in case when model (user) is not authorized
|
24
|
+
# }
|
25
|
+
end
|
26
|
+
"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable all
|
4
|
+
module Ez
|
5
|
+
module Permissions
|
6
|
+
class MigrationsGenerator < Rails::Generators::Base
|
7
|
+
def config
|
8
|
+
Ez::Permissions.config
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_migration
|
12
|
+
create_file "db/migrate/#{Time.current.strftime('%Y%m%d%H%M%S')}_create_ez_permissions_roles.rb",
|
13
|
+
"# frozen_string_literal: true
|
14
|
+
|
15
|
+
class CreateEzPermissionsRoles < ActiveRecord::Migration[5.0]
|
16
|
+
def change
|
17
|
+
create_table :#{config.roles_table_name} do |t|
|
18
|
+
t.string :name, null: false
|
19
|
+
t.timestamps null: false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
"
|
24
|
+
create_file "db/migrate/#{(Time.current + 1).strftime('%Y%m%d%H%M%S')}_create_ez_permissions_permissions.rb",
|
25
|
+
"# frozen_string_literal: true
|
26
|
+
|
27
|
+
class CreateEzPermissionsPermissions < ActiveRecord::Migration[5.0]
|
28
|
+
def change
|
29
|
+
create_table :#{config.permissions_table_name} do |t|
|
30
|
+
t.string :resource, index: true, null: false
|
31
|
+
t.string :action, index: true, null: false
|
32
|
+
t.timestamps null: false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
"
|
37
|
+
create_file "db/migrate/#{(Time.current + 2).strftime('%Y%m%d%H%M%S')}_create_ez_permissions_model_roles.rb",
|
38
|
+
"# frozen_string_literal: true
|
39
|
+
|
40
|
+
class CreateEzPermissionsModelRoles < ActiveRecord::Migration[5.0]
|
41
|
+
def change
|
42
|
+
create_table :#{config.models_roles_table_name} do |t|
|
43
|
+
t.integer :model_id, index: true, null: false
|
44
|
+
t.string :model_type, index: true, null: false
|
45
|
+
|
46
|
+
t.integer :scoped_id, index: true
|
47
|
+
t.string :scoped_type, index: true
|
48
|
+
|
49
|
+
t.integer :role_id, index: true, null: false
|
50
|
+
|
51
|
+
t.timestamps null: false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
"
|
56
|
+
|
57
|
+
create_file "db/migrate/#{(Time.current + 3).strftime('%Y%m%d%H%M%S')}_create_ez_permissions_permissions_roles.rb",
|
58
|
+
"# frozen_string_literal: true
|
59
|
+
|
60
|
+
class CreateEzPermissionsPermissionsRoles < ActiveRecord::Migration[5.0]
|
61
|
+
def change
|
62
|
+
create_table :#{config.permissions_roles_table_name} do |t|
|
63
|
+
t.integer :permission_id, index: true, null: false
|
64
|
+
t.integer :role_id, index: true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
# rubocop: enable all
|
metadata
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ez-permissions
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Volodya Sveredyuk
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-02-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ez-core
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.1.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.1.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: faker
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard-rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry-rails
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec-rails
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rubocop
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: simplecov
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: sqlite3
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 1.3.6
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 1.3.6
|
153
|
+
description: Easy permissions engine for Rails app.
|
154
|
+
email:
|
155
|
+
- sveredyuk@gmail.com
|
156
|
+
executables: []
|
157
|
+
extensions: []
|
158
|
+
extra_rdoc_files: []
|
159
|
+
files:
|
160
|
+
- MIT-LICENSE
|
161
|
+
- README.md
|
162
|
+
- Rakefile
|
163
|
+
- app/controllers/ez/permissions/application_controller.rb
|
164
|
+
- app/models/ez/permissions/application_record.rb
|
165
|
+
- app/models/ez/permissions/model.rb
|
166
|
+
- app/models/ez/permissions/model_role.rb
|
167
|
+
- app/models/ez/permissions/permission.rb
|
168
|
+
- app/models/ez/permissions/permission_role.rb
|
169
|
+
- app/models/ez/permissions/role.rb
|
170
|
+
- config/routes.rb
|
171
|
+
- lib/ez/permissions.rb
|
172
|
+
- lib/ez/permissions/api.rb
|
173
|
+
- lib/ez/permissions/api/authorize.rb
|
174
|
+
- lib/ez/permissions/api/models.rb
|
175
|
+
- lib/ez/permissions/api/permissions.rb
|
176
|
+
- lib/ez/permissions/api/roles.rb
|
177
|
+
- lib/ez/permissions/dsl.rb
|
178
|
+
- lib/ez/permissions/engine.rb
|
179
|
+
- lib/ez/permissions/railtie.rb
|
180
|
+
- lib/ez/permissions/resource.rb
|
181
|
+
- lib/ez/permissions/version.rb
|
182
|
+
- lib/generators/ez/permissions/install_generator.rb
|
183
|
+
- lib/generators/ez/permissions/migrations_generator.rb
|
184
|
+
- lib/tasks/ez/permissions_tasks.rake
|
185
|
+
homepage: https://github.com/ez-permissions
|
186
|
+
licenses:
|
187
|
+
- MIT
|
188
|
+
metadata:
|
189
|
+
allowed_push_host: https://rubygems.org
|
190
|
+
post_install_message:
|
191
|
+
rdoc_options: []
|
192
|
+
require_paths:
|
193
|
+
- lib
|
194
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
195
|
+
requirements:
|
196
|
+
- - ">="
|
197
|
+
- !ruby/object:Gem::Version
|
198
|
+
version: '0'
|
199
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
200
|
+
requirements:
|
201
|
+
- - ">="
|
202
|
+
- !ruby/object:Gem::Version
|
203
|
+
version: '0'
|
204
|
+
requirements: []
|
205
|
+
rubyforge_project:
|
206
|
+
rubygems_version: 2.7.6
|
207
|
+
signing_key:
|
208
|
+
specification_version: 4
|
209
|
+
summary: Easy permissions engine for Rails app.
|
210
|
+
test_files: []
|