do_role 0.2.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/README.md +141 -0
- data/Rakefile +32 -0
- data/lib/do_role/ability.rb +82 -0
- data/lib/do_role/concerns/model.rb +28 -0
- data/lib/do_role/concerns/permission_manageable.rb +130 -0
- data/lib/do_role/concerns/role_model.rb +52 -0
- data/lib/do_role/conditions_proxy.rb +18 -0
- data/lib/do_role/permission_audit.rb +36 -0
- data/lib/do_role/permission_set.rb +137 -0
- data/lib/do_role/permission_set_manager.rb +44 -0
- data/lib/do_role/role.rb +19 -0
- data/lib/do_role/version.rb +5 -0
- data/lib/do_role.rb +57 -0
- data/lib/generators/do_role/install/install_generator.rb +30 -0
- data/lib/generators/do_role/install/templates/create_do_role_tables.rb +24 -0
- data/lib/generators/do_role/install/templates/do_role.rb +21 -0
- data/lib/generators/do_role/templates/permission_form.html.erb +24 -0
- metadata +145 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0e0656ea0457c67f9aff8a0e70bde325a857891a4e6e12df2ac9b29df757a330
|
|
4
|
+
data.tar.gz: b178335b7e1938fcb1aae1d49c0592995db7cc62236ec0b26bc369d08e4e4aff
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1494eb63a948ff541d2473bb5c119b0783e14b2e10b0f36758b72e11058e647e2358c63750ce7866c2335a7bf7cf97f81f0b9e4784e7a54d1dd4ebcc079af4bf
|
|
7
|
+
data.tar.gz: f3b52ea6aea1c23688e5ac48ed0e4fae822e0c0ac584bd3a857ef8ac523604c26322ffe7fea0617c2bd6a8188b50b73c41a8d7541ff96bd0a7f330b09a13ba05
|
data/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# DoRole
|
|
2
|
+
|
|
3
|
+
一个简单而灵活的Ruby on Rails角色权限管理解决方案。DoRole提供了直观的方式来管理Rails应用中的角色和权限。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- 简单直观的API设计
|
|
8
|
+
- 灵活的权限配置系统
|
|
9
|
+
- 支持权限继承
|
|
10
|
+
- 支持条件权限
|
|
11
|
+
- 内置权限审计功能
|
|
12
|
+
- 完全兼容Rails 7.0+
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
在你的Gemfile中添加:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
gem 'do_role'
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
然后执行:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
$ bundle install
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 基本配置
|
|
29
|
+
|
|
30
|
+
### 生成迁移文件
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
$ rails generate do_role:install
|
|
34
|
+
$ rails db:migrate
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 配置权限
|
|
38
|
+
|
|
39
|
+
在`config/initializers/do_role.rb`中定义权限:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
DoRole.permission_set do
|
|
43
|
+
# 使用namespace组织权限
|
|
44
|
+
namespace :posts do
|
|
45
|
+
permission :index
|
|
46
|
+
permission :create
|
|
47
|
+
permission :update
|
|
48
|
+
permission :destroy
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
namespace :users do
|
|
52
|
+
permission :manage
|
|
53
|
+
permission :view
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# 可以添加自定义选项
|
|
57
|
+
namespace :admin do
|
|
58
|
+
permission :dashboard, description: "访问管理后台"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# 支持权限继承
|
|
62
|
+
namespace :articles do
|
|
63
|
+
permission :create
|
|
64
|
+
permission :update
|
|
65
|
+
permission :destroy
|
|
66
|
+
permission :manage, requires: [:create, :update, :destroy]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# 条件权限
|
|
70
|
+
namespace :comments do
|
|
71
|
+
permission :moderate, user_id: ->(user_id) { user_id == @user.id }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 使用方法
|
|
77
|
+
|
|
78
|
+
### 在模型中包含DoRole
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
class User < ApplicationRecord
|
|
82
|
+
include DoRole::Model
|
|
83
|
+
# 自动提供以下方法:
|
|
84
|
+
# - roles
|
|
85
|
+
# - permissions
|
|
86
|
+
# - has_permission?
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 创建和管理角色
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
# 创建角色
|
|
94
|
+
role = user.roles.create(name: "编辑", permissions: ["posts.create", "posts.update"])
|
|
95
|
+
|
|
96
|
+
# 添加权限
|
|
97
|
+
role.add_permission("posts.destroy")
|
|
98
|
+
|
|
99
|
+
# 移除权限
|
|
100
|
+
role.remove_permission("posts.destroy")
|
|
101
|
+
|
|
102
|
+
# 检查权限
|
|
103
|
+
role.has_permission?("posts.create") # => true
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 用户权限验证
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
# 检查用户是否拥有特定权限
|
|
110
|
+
user.has_permission?("posts.create")
|
|
111
|
+
|
|
112
|
+
# 带条件的权限检查
|
|
113
|
+
user.has_permission?("comments.moderate", comment)
|
|
114
|
+
|
|
115
|
+
# 获取用户所有权限
|
|
116
|
+
user.permissions # => ["posts.create", "posts.update", ...]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 权限审计
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
# 启用权限审计
|
|
123
|
+
DoRole.configure do |config|
|
|
124
|
+
config.enable_permission_audit = true
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# 查看权限变更记录
|
|
128
|
+
user.permission_audits
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 贡献
|
|
132
|
+
|
|
133
|
+
1. Fork 项目
|
|
134
|
+
2. 创建特性分支 (`git checkout -b my-new-feature`)
|
|
135
|
+
3. 提交你的改动 (`git commit -am 'Add some feature'`)
|
|
136
|
+
4. 推送到分支 (`git push origin my-new-feature`)
|
|
137
|
+
5. 创建一个 Pull Request
|
|
138
|
+
|
|
139
|
+
## 许可证
|
|
140
|
+
|
|
141
|
+
本项目基于 [MIT License](https://opensource.org/licenses/MIT) 开源。
|
data/Rakefile
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
|
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
|
7
|
+
t.libs << "test"
|
|
8
|
+
t.libs << "lib"
|
|
9
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
|
10
|
+
t.warning = false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
Rake::TestTask.new(:integration) do |t|
|
|
14
|
+
t.libs << "test"
|
|
15
|
+
t.libs << "lib"
|
|
16
|
+
t.test_files = FileList["test/integration/**/*_test.rb"]
|
|
17
|
+
t.warning = false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
task default: :test
|
|
21
|
+
|
|
22
|
+
desc "运行所有测试(单元测试和集成测试)"
|
|
23
|
+
task :all_tests do
|
|
24
|
+
Rake::Task["test"].invoke
|
|
25
|
+
Rake::Task["integration"].invoke
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
desc "构建和安装gem包"
|
|
29
|
+
task :build_and_install do
|
|
30
|
+
system "gem build do_role.gemspec"
|
|
31
|
+
system "gem install do_role-*.gem"
|
|
32
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DoRole
|
|
4
|
+
class Ability
|
|
5
|
+
attr_reader :user
|
|
6
|
+
|
|
7
|
+
def initialize(user)
|
|
8
|
+
@user = user
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def can?(action, subject, *extra_args)
|
|
12
|
+
conditions = extra_args.extract_options!
|
|
13
|
+
permission_name = build_permission_name(action, subject)
|
|
14
|
+
|
|
15
|
+
return false unless has_base_permission?(permission_name)
|
|
16
|
+
|
|
17
|
+
permission = DoRole.permission_set.get(permission_name)
|
|
18
|
+
if permission && permission[:requires].present?
|
|
19
|
+
return false unless permission[:requires].all? { |req| can?(req, subject, conditions) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# # 如果是对类进行检查且没有条件权限,则只检查基本权限
|
|
23
|
+
# return false if subject.is_a?(Class) && permission[:conditions].present?
|
|
24
|
+
# # 如果是对类进行检查且有条件权限,则返回false
|
|
25
|
+
return true if subject.is_a?(Class) #&& permission[:conditions].empty?
|
|
26
|
+
|
|
27
|
+
conditions_to_check = conditions
|
|
28
|
+
if permission && !permission[:conditions].empty?
|
|
29
|
+
conditions_to_check = permission[:conditions].merge(conditions)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
check_conditions(subject, conditions_to_check)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def has_base_permission?(permission_name)
|
|
38
|
+
namespace, action = permission_name.to_s.split('.')
|
|
39
|
+
return false unless namespace && action
|
|
40
|
+
user.roles.any? { |role| role.has_permission?(permission_name) } ||
|
|
41
|
+
user.permissions[namespace]&.include?(action)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def check_conditions(subject, conditions)
|
|
45
|
+
return true if conditions.empty?
|
|
46
|
+
|
|
47
|
+
conditions.all? do |key, value|
|
|
48
|
+
subject_value = if subject.is_a?(Hash)
|
|
49
|
+
subject[key]
|
|
50
|
+
else
|
|
51
|
+
subject.respond_to?(key) ? subject.public_send(key) : nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if value.is_a?(Proc)
|
|
55
|
+
instance_exec(subject_value, &value)
|
|
56
|
+
else
|
|
57
|
+
subject_value == value
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def build_permission_name(action, subject)
|
|
63
|
+
subject_name = if subject.is_a?(Class)
|
|
64
|
+
if defined?(ActiveRecord) && subject.respond_to?(:base_class)
|
|
65
|
+
subject.base_class.name.underscore
|
|
66
|
+
else
|
|
67
|
+
subject.superclass.name.underscore
|
|
68
|
+
end
|
|
69
|
+
elsif subject.is_a?(Hash)
|
|
70
|
+
subject[:resource_type]&.to_s&.underscore || raise(ArgumentError, "resource_type is required when subject is a Hash")
|
|
71
|
+
else
|
|
72
|
+
get_base_class_name(subject.class)
|
|
73
|
+
end
|
|
74
|
+
[subject_name.pluralize, action].join('.')
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def get_base_class_name(klass)
|
|
78
|
+
return klass.name.underscore if !defined?(ActiveRecord) || !klass.respond_to?(:base_class)
|
|
79
|
+
klass.base_class.name.underscore
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module DoRole
|
|
2
|
+
module Model
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
class_methods do
|
|
6
|
+
def do_role(options = {})
|
|
7
|
+
role_association = options[:role_association] || :user_roles
|
|
8
|
+
role_class = options[:role_class] || 'DoRole::Role'
|
|
9
|
+
|
|
10
|
+
has_many role_association, dependent: :destroy
|
|
11
|
+
has_many :roles, through: role_association, class_name: role_class
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
included do
|
|
16
|
+
do_role
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def has_permission?(permission)
|
|
20
|
+
return false unless permission
|
|
21
|
+
roles.any? { |role| role.has_permission?(permission) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def computed_permissions
|
|
25
|
+
roles.map(&:computed_permissions).flatten.uniq
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DoRole
|
|
4
|
+
module PermissionManageable
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
serialize :permissions, coder: JSON
|
|
9
|
+
validates :permissions, presence: true
|
|
10
|
+
|
|
11
|
+
after_initialize :initialize_permissions
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# 检查是否拥有某个权限
|
|
15
|
+
def has_permission?(permission)
|
|
16
|
+
return false if permission.blank?
|
|
17
|
+
namespace, action = parse_permission(permission)
|
|
18
|
+
cached_permissions.dig(namespace)&.include?(action)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# 添加权限
|
|
22
|
+
def add_permission(permission)
|
|
23
|
+
validate_permission!(permission)
|
|
24
|
+
modify_permission(permission, :add)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# 移除权限
|
|
28
|
+
def remove_permission(permission)
|
|
29
|
+
return if permission.blank?
|
|
30
|
+
modify_permission(permission, :remove)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# 获取所有权限
|
|
34
|
+
def all_permissions
|
|
35
|
+
cached_permissions
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# 计算完整权限路径
|
|
39
|
+
def computed_permissions
|
|
40
|
+
all_permissions.flat_map do |namespace, actions|
|
|
41
|
+
if actions.is_a?(Hash)
|
|
42
|
+
actions.flat_map do |sub_namespace, sub_actions|
|
|
43
|
+
Array.wrap(sub_actions).map { |action| "#{namespace}.#{sub_namespace}.#{action}" }
|
|
44
|
+
end
|
|
45
|
+
else
|
|
46
|
+
Array.wrap(actions).map { |action| "#{namespace}.#{action}" }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# 带缓存的权限
|
|
52
|
+
def cached_permissions
|
|
53
|
+
@cached_permissions ||= begin
|
|
54
|
+
all_permissions = permissions.deep_dup
|
|
55
|
+
if parent_role
|
|
56
|
+
parent_permissions = parent_role.cached_permissions
|
|
57
|
+
all_permissions.deep_merge!(parent_permissions) { |_, old_val, new_val| Array.wrap(old_val) | Array.wrap(new_val) }
|
|
58
|
+
end
|
|
59
|
+
all_permissions
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
# 初始化权限
|
|
66
|
+
def initialize_permissions
|
|
67
|
+
self.permissions = permissions.presence || {}.with_indifferent_access
|
|
68
|
+
self.permissions = convert_array_to_hash if permissions.is_a?(Array)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# 修改权限(添加或删除)
|
|
72
|
+
def modify_permission(permission, action)
|
|
73
|
+
namespace, action_name = parse_permission(permission)
|
|
74
|
+
current_permissions = permissions.deep_dup
|
|
75
|
+
|
|
76
|
+
*path, last = namespace.split('.')
|
|
77
|
+
target = path.inject(current_permissions) { |hash, ns| hash[ns] ||= {} }
|
|
78
|
+
|
|
79
|
+
case action
|
|
80
|
+
when :add
|
|
81
|
+
target[last] = Array.wrap(target[last]) | [action_name]
|
|
82
|
+
when :remove
|
|
83
|
+
if actions = target[last]
|
|
84
|
+
Array.wrap(actions).delete(action_name)
|
|
85
|
+
target.delete(last) if actions.blank?
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
self.permissions = current_permissions
|
|
90
|
+
clear_permissions_cache
|
|
91
|
+
save if persisted?
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# 解析权限字符串
|
|
95
|
+
def parse_permission(permission)
|
|
96
|
+
raise ArgumentError, "Permission cannot be nil or empty" if permission.blank?
|
|
97
|
+
permission.to_s.strip.split('.').then { |parts| [parts[0..-2].join('.'), parts.last] }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# 验证权限格式
|
|
101
|
+
def validate_permission!(permission)
|
|
102
|
+
normalized = normalize_permission(permission)
|
|
103
|
+
raise ArgumentError, "Invalid permission format" unless normalized.match?(/\A[\w]+(?:\.[\w]+)+\z/)
|
|
104
|
+
raise ArgumentError, "Permission must contain only letters, numbers, and dots" unless normalized.match?(/\A[a-zA-Z0-9_.]+\z/)
|
|
105
|
+
raise ArgumentError if normalized != permission
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# 规范化权限字符串
|
|
109
|
+
def normalize_permission(permission)
|
|
110
|
+
permission.to_s.strip.downcase
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# 将数组格式的权限转换为哈希
|
|
114
|
+
def convert_array_to_hash
|
|
115
|
+
return unless permissions.is_a?(Array)
|
|
116
|
+
permissions.select(&:present?).map(&:strip).each_with_object({}.with_indifferent_access) do |permission, hash|
|
|
117
|
+
namespace, action = parse_permission(permission)
|
|
118
|
+
*path, last = namespace.split('.')
|
|
119
|
+
current = path.inject(hash) { |h, ns| h[ns] ||= {} }
|
|
120
|
+
current[last] = Array.wrap(current[last]) | [action]
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# 清除权限缓存
|
|
125
|
+
def clear_permissions_cache
|
|
126
|
+
@cached_permissions = nil
|
|
127
|
+
child_roles.each(&:clear_permissions_cache)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DoRole
|
|
4
|
+
module RoleModel
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
has_many :user_roles, dependent: :destroy
|
|
9
|
+
has_many :roles, through: :user_roles
|
|
10
|
+
|
|
11
|
+
serialize :permissions, coder: JSON
|
|
12
|
+
|
|
13
|
+
after_initialize do
|
|
14
|
+
self.permissions ||= {}
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def has_permission?(permission)
|
|
19
|
+
return true if check_direct_permission(permission)
|
|
20
|
+
return true if roles.any? { |role| role.has_permission?(permission) }
|
|
21
|
+
false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def add_permission(permission)
|
|
25
|
+
self.permissions = (Array(permissions) + [permission.to_s]).uniq
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def remove_permission(permission)
|
|
29
|
+
return unless permission
|
|
30
|
+
self.permissions = Array(permissions) - [permission.to_s]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def computed_permissions
|
|
34
|
+
role_permissions = roles.map(&:computed_permissions).flatten
|
|
35
|
+
direct_permissions = DoRole.permission_set.computed_permissions(permissions)
|
|
36
|
+
(role_permissions + direct_permissions).uniq
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def check_direct_permission(permission)
|
|
42
|
+
return false unless permission
|
|
43
|
+
permissions.include?(permission.to_s)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def parse_permission(permission)
|
|
47
|
+
permission = permission.to_s.strip
|
|
48
|
+
namespace, action = permission.split('.')
|
|
49
|
+
[namespace, action]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DoRole
|
|
4
|
+
class ConditionsProxy
|
|
5
|
+
attr_reader :conditions
|
|
6
|
+
def initialize(conditions)
|
|
7
|
+
@conditions = conditions
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def condition(name, value)
|
|
11
|
+
@conditions[name.to_sym] = value
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def requires(*permissions)
|
|
15
|
+
@conditions[:requires] = permissions.flatten.map(&:to_s)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DoRole
|
|
4
|
+
class PermissionAudit
|
|
5
|
+
class << self
|
|
6
|
+
def log_changes(action, namespace, permission_name, options = {})
|
|
7
|
+
@audit_logs ||= []
|
|
8
|
+
@audit_logs << {
|
|
9
|
+
action: action,
|
|
10
|
+
namespace: namespace,
|
|
11
|
+
permission_name: permission_name,
|
|
12
|
+
options: options,
|
|
13
|
+
timestamp: Time.now
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def clear_logs
|
|
18
|
+
@audit_logs = []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def get_logs(filters = {})
|
|
22
|
+
return [] if @audit_logs.nil?
|
|
23
|
+
|
|
24
|
+
logs = @audit_logs.dup
|
|
25
|
+
logs = logs.select { |log| log[:namespace] == filters[:namespace] } if filters[:namespace]
|
|
26
|
+
logs = logs.select { |log| log[:action] == filters[:action] } if filters[:action]
|
|
27
|
+
logs
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def get_recent_logs(limit = 10)
|
|
31
|
+
return [] if @audit_logs.nil?
|
|
32
|
+
@audit_logs.last(limit)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DoRole
|
|
4
|
+
class PermissionSet
|
|
5
|
+
attr_reader :registered_permissions
|
|
6
|
+
alias :permissions :registered_permissions
|
|
7
|
+
|
|
8
|
+
RESERVED_OPTIONS = [:priority, :description, :requires, :default, :conditions].freeze
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@registered_permissions = {}.with_indifferent_access
|
|
12
|
+
@namespace_stack = []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def draw(&block)
|
|
16
|
+
instance_eval(&block) if block_given?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def namespace(name, options = {}, &block)
|
|
20
|
+
raise ArgumentError, "name can't be blank" if name.blank?
|
|
21
|
+
|
|
22
|
+
name = name.to_s
|
|
23
|
+
@namespace_stack.push(name)
|
|
24
|
+
|
|
25
|
+
instance_eval(&block) if block_given?
|
|
26
|
+
|
|
27
|
+
@namespace_stack.pop
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def permission(name, options = {}, &block)
|
|
31
|
+
name = name.to_s
|
|
32
|
+
raise ArgumentError, "name can't be blank" if name.blank?
|
|
33
|
+
|
|
34
|
+
conditions = if block_given?
|
|
35
|
+
ConditionsProxy.new({}).tap { |proxy| proxy.instance_eval(&block) }.conditions
|
|
36
|
+
else
|
|
37
|
+
non_reserved_options = options.except(*RESERVED_OPTIONS)
|
|
38
|
+
options.except!(*non_reserved_options.keys)
|
|
39
|
+
non_reserved_options
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
options.reverse_merge!(
|
|
43
|
+
conditions: conditions,
|
|
44
|
+
priority: 0,
|
|
45
|
+
description: "",
|
|
46
|
+
requires: Array.wrap(options[:requires]).map(&:to_s)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
register_permission(name, options)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def get(permission_name)
|
|
53
|
+
permission = registered_permissions[permission_name.to_s]
|
|
54
|
+
return permission if permission&.key?(:priority)
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def valid_permission?(permission_name)
|
|
59
|
+
permission = get(permission_name)
|
|
60
|
+
!permission.nil?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def computed_permissions(base_permissions)
|
|
64
|
+
base_permissions
|
|
65
|
+
.map(&:to_s)
|
|
66
|
+
.select { |p| valid_permission?(p) }
|
|
67
|
+
.tap { |result| result.concat(collect_required_permissions(result)) }
|
|
68
|
+
.uniq
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def namespaces
|
|
72
|
+
collect_namespaces(registered_permissions, [], []).uniq
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def permissions_in_namespace(namespace)
|
|
76
|
+
current = registered_permissions.dig(*namespace.to_s.split('.'))
|
|
77
|
+
return {} unless current.is_a?(Hash)
|
|
78
|
+
|
|
79
|
+
flatten_permissions(current, namespace.to_s)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def collect_required_permissions(permissions)
|
|
85
|
+
permissions.flat_map do |permission|
|
|
86
|
+
permission_config = get(permission)
|
|
87
|
+
next [] unless permission_config&.dig(:requires)&.present?
|
|
88
|
+
|
|
89
|
+
permission_config[:requires].map { |req| build_full_permission_path(permission, req) }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def build_full_permission_path(permission, req)
|
|
94
|
+
return req if req.include?('.')
|
|
95
|
+
|
|
96
|
+
namespace = permission.split('.')[0..-2].join('.')
|
|
97
|
+
"#{namespace}.#{req}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def collect_namespaces(hash, current_path, result)
|
|
101
|
+
hash.each do |key, value|
|
|
102
|
+
if value.is_a?(Hash) && RESERVED_OPTIONS.none? { |opt| value.key?(opt) }
|
|
103
|
+
result << (current_path + [key]).join('.')
|
|
104
|
+
collect_namespaces(value, current_path + [key], result)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
result
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def flatten_permissions(hash, prefix)
|
|
111
|
+
hash.each_with_object({}) do |(key, value), result|
|
|
112
|
+
next unless value.is_a?(Hash)
|
|
113
|
+
|
|
114
|
+
full_key = prefix.presence ? "#{prefix}.#{key}" : key
|
|
115
|
+
if (RESERVED_OPTIONS & value.keys).present?
|
|
116
|
+
result[full_key] = value
|
|
117
|
+
else
|
|
118
|
+
result.merge!(flatten_permissions(value, full_key))
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def register_permission(name, options = {})
|
|
124
|
+
options.reverse_merge!(conditions: {}, priority: 0, description: "")
|
|
125
|
+
options[:requires] = Array.wrap(options[:requires]).map(&:to_s) if options[:requires].present?
|
|
126
|
+
|
|
127
|
+
full_path = (@namespace_stack + [name]).join('.')
|
|
128
|
+
|
|
129
|
+
current_hash = @namespace_stack.inject(registered_permissions) do |hash, part|
|
|
130
|
+
hash[part] ||= {}
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
current_hash[name] = options.deep_dup
|
|
134
|
+
registered_permissions[full_path] = options.deep_dup
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DoRole
|
|
4
|
+
class PermissionSetManager
|
|
5
|
+
delegate :clear, to: :@permission_sets
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@permission_sets = {}.with_indifferent_access
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def for_namespace(namespace)
|
|
12
|
+
@permission_sets[namespace.to_s] ||= PermissionSet.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def namespaces
|
|
16
|
+
@permission_sets.keys
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def namespace_exists?(namespace)
|
|
20
|
+
@permission_sets.key?(namespace.to_s)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def remove_namespace(namespace)
|
|
24
|
+
@permission_sets.delete(namespace.to_s)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def permissions_for(namespace)
|
|
28
|
+
@permission_sets[namespace.to_s].try(:permissions) || {}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def valid_permission?(namespace, permission_name)
|
|
32
|
+
@permission_sets[namespace.to_s].try(:valid_permission?, permission_name)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def set_inheritance(namespace, inherit_from)
|
|
36
|
+
target_set = for_namespace(namespace)
|
|
37
|
+
Array(inherit_from).each do |parent_namespace|
|
|
38
|
+
if (parent_set = @permission_sets[parent_namespace])
|
|
39
|
+
target_set.registered_permissions.deep_merge!(parent_set.registered_permissions)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/do_role/role.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DoRole
|
|
4
|
+
class Role < ActiveRecord::Base
|
|
5
|
+
include PermissionManageable
|
|
6
|
+
|
|
7
|
+
has_many :user_roles, dependent: :destroy
|
|
8
|
+
has_many :users, through: :user_roles
|
|
9
|
+
belongs_to :parent_role, class_name: 'DoRole::Role', optional: true
|
|
10
|
+
has_many :child_roles, class_name: 'DoRole::Role', foreign_key: 'parent_role_id'
|
|
11
|
+
|
|
12
|
+
validates :name, presence: true
|
|
13
|
+
|
|
14
|
+
def self.define_role_association(association_name, options = {})
|
|
15
|
+
has_many association_name, options.merge(dependent: :destroy)
|
|
16
|
+
has_many options[:through_model].to_s.pluralize.to_sym, through: association_name if options[:through_model]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/do_role.rb
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record"
|
|
4
|
+
require "active_support"
|
|
5
|
+
require "do_role/version"
|
|
6
|
+
require "do_role/conditions_proxy"
|
|
7
|
+
require "do_role/concerns/model"
|
|
8
|
+
require "do_role/concerns/role_model"
|
|
9
|
+
require "do_role/concerns/permission_manageable"
|
|
10
|
+
require "do_role/permission_audit"
|
|
11
|
+
require "do_role/permission_set"
|
|
12
|
+
require "do_role/permission_set_manager"
|
|
13
|
+
require "do_role/ability"
|
|
14
|
+
require "do_role/role"
|
|
15
|
+
|
|
16
|
+
module DoRole
|
|
17
|
+
class Error < StandardError; end
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
def permission_set_manager
|
|
21
|
+
@permission_set_manager ||= PermissionSetManager.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def permission_set(namespace = :default, inherit_from: [], &block)
|
|
25
|
+
set = permission_set_manager.for_namespace(namespace)
|
|
26
|
+
if inherit_from.any?
|
|
27
|
+
permission_set_manager.set_inheritance(namespace, inherit_from)
|
|
28
|
+
end
|
|
29
|
+
set.draw(&block) if block_given?
|
|
30
|
+
set
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def reset_permissions(namespace = :default)
|
|
34
|
+
if namespace == :all
|
|
35
|
+
permission_set_manager.clear
|
|
36
|
+
else
|
|
37
|
+
permission_set_manager.remove_namespace(namespace)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def permissions(namespace = :default)
|
|
42
|
+
permission_set_manager.permissions_for(namespace)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def valid_permission?(permission_name, namespace = :default)
|
|
46
|
+
permission_set_manager.valid_permission?(namespace, permission_name)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def ability_for(user)
|
|
50
|
+
Ability.new(user)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# ActiveSupport.on_load(:active_record) do
|
|
56
|
+
# include DoRole::Model
|
|
57
|
+
# end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DoRole
|
|
4
|
+
module Generators
|
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
|
6
|
+
include Rails::Generators::Migration
|
|
7
|
+
|
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
|
9
|
+
|
|
10
|
+
def self.next_migration_number(dirname)
|
|
11
|
+
if ActiveRecord::Base.timestamped_migrations
|
|
12
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
13
|
+
else
|
|
14
|
+
format("%03d", current_migration_number(dirname) + 1)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create_migration_file
|
|
19
|
+
migration_template(
|
|
20
|
+
'create_do_role_tables.rb',
|
|
21
|
+
'db/migrate/create_do_role_tables.rb'
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def create_initializer
|
|
26
|
+
template 'do_role.rb', 'config/initializers/do_role.rb'
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateDoRoleTables < ActiveRecord::Migration[7.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :roles do |t|
|
|
6
|
+
t.string :name, null: false
|
|
7
|
+
t.text :permissions
|
|
8
|
+
t.text :description
|
|
9
|
+
|
|
10
|
+
t.timestamps
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
add_index :roles, :name, unique: true
|
|
14
|
+
|
|
15
|
+
create_table :user_roles do |t|
|
|
16
|
+
t.references :user, null: false, foreign_key: true
|
|
17
|
+
t.references :role, null: false, foreign_key: true
|
|
18
|
+
|
|
19
|
+
t.timestamps
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
add_index :user_roles, [:user_id, :role_id], unique: true
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
DoRole.permission_set do
|
|
4
|
+
# 使用namespace组织权限
|
|
5
|
+
namespace :posts do
|
|
6
|
+
permission :index
|
|
7
|
+
permission :create
|
|
8
|
+
permission :update
|
|
9
|
+
permission :destroy
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
namespace :users do
|
|
13
|
+
permission :manage
|
|
14
|
+
permission :view
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# 可以添加自定义选项
|
|
18
|
+
namespace :admin do
|
|
19
|
+
permission :dashboard, description: "访问管理后台"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<%= form_with model: @role do |f| %>
|
|
2
|
+
<div class="permission-form">
|
|
3
|
+
<% DoRole.permission_set.registered_permissions.group_by { |name, _| name.split('.').first }.each do |namespace, permissions| %>
|
|
4
|
+
<div class="permission-group">
|
|
5
|
+
<h3><%= namespace.titleize %></h3>
|
|
6
|
+
<div class="permission-items">
|
|
7
|
+
<% permissions.each do |permission_name, permission| %>
|
|
8
|
+
<div class="permission-item">
|
|
9
|
+
<%= check_box_tag "role[permissions][]", permission_name, @role.has_permission?(permission_name), id: "permission_#{permission_name.gsub('.', '_')}" %>
|
|
10
|
+
<%= label_tag "permission_#{permission_name.gsub('.', '_')}", permission_name %>
|
|
11
|
+
<% if permission[:description].present? %>
|
|
12
|
+
<small class="permission-description"><%= permission[:description] %></small>
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
15
|
+
<% end %>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
<% end %>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div class="form-actions">
|
|
22
|
+
<%= f.submit "保存", class: "btn btn-primary" %>
|
|
23
|
+
</div>
|
|
24
|
+
<% end %>
|
metadata
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: do_role
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- doabit
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-02-21 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 7.0.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 7.0.0
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activesupport
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 7.0.0
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 7.0.0
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: activerecord
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: 7.0.0
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: 7.0.0
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: sqlite3
|
|
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: minitest
|
|
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: rake
|
|
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
|
+
description: DoRole provides a clean and intuitive way to manage roles and permissions
|
|
98
|
+
in Rails applications
|
|
99
|
+
email:
|
|
100
|
+
- doinsist@gmail.com
|
|
101
|
+
executables: []
|
|
102
|
+
extensions: []
|
|
103
|
+
extra_rdoc_files: []
|
|
104
|
+
files:
|
|
105
|
+
- README.md
|
|
106
|
+
- Rakefile
|
|
107
|
+
- lib/do_role.rb
|
|
108
|
+
- lib/do_role/ability.rb
|
|
109
|
+
- lib/do_role/concerns/model.rb
|
|
110
|
+
- lib/do_role/concerns/permission_manageable.rb
|
|
111
|
+
- lib/do_role/concerns/role_model.rb
|
|
112
|
+
- lib/do_role/conditions_proxy.rb
|
|
113
|
+
- lib/do_role/permission_audit.rb
|
|
114
|
+
- lib/do_role/permission_set.rb
|
|
115
|
+
- lib/do_role/permission_set_manager.rb
|
|
116
|
+
- lib/do_role/role.rb
|
|
117
|
+
- lib/do_role/version.rb
|
|
118
|
+
- lib/generators/do_role/install/install_generator.rb
|
|
119
|
+
- lib/generators/do_role/install/templates/create_do_role_tables.rb
|
|
120
|
+
- lib/generators/do_role/install/templates/do_role.rb
|
|
121
|
+
- lib/generators/do_role/templates/permission_form.html.erb
|
|
122
|
+
homepage: https://github.com/doabit/do_role
|
|
123
|
+
licenses:
|
|
124
|
+
- MIT
|
|
125
|
+
metadata: {}
|
|
126
|
+
post_install_message:
|
|
127
|
+
rdoc_options: []
|
|
128
|
+
require_paths:
|
|
129
|
+
- lib
|
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
131
|
+
requirements:
|
|
132
|
+
- - ">="
|
|
133
|
+
- !ruby/object:Gem::Version
|
|
134
|
+
version: '0'
|
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
136
|
+
requirements:
|
|
137
|
+
- - ">="
|
|
138
|
+
- !ruby/object:Gem::Version
|
|
139
|
+
version: '0'
|
|
140
|
+
requirements: []
|
|
141
|
+
rubygems_version: 3.5.22
|
|
142
|
+
signing_key:
|
|
143
|
+
specification_version: 4
|
|
144
|
+
summary: A simple and flexible role management solution for Rails applications
|
|
145
|
+
test_files: []
|