roleback 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +37 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +44 -0
- data/README.md +226 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/roleback/configuration.rb +138 -0
- data/lib/roleback/definitions/load.rb +2 -0
- data/lib/roleback/definitions/resource.rb +68 -0
- data/lib/roleback/definitions/role.rb +98 -0
- data/lib/roleback/definitions/rule_based.rb +39 -0
- data/lib/roleback/definitions/scope.rb +33 -0
- data/lib/roleback/outcome.rb +61 -0
- data/lib/roleback/rule.rb +65 -0
- data/lib/roleback/rule_book.rb +119 -0
- data/lib/roleback/user_extension.rb +33 -0
- data/lib/roleback/version.rb +3 -0
- data/lib/roleback.rb +12 -0
- data/roleback.gemspec +40 -0
- metadata +125 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ba408ae577a2cf4e25474b107715294f454a7d1f9c99b390d4f19b10e4c9e355
|
4
|
+
data.tar.gz: 268e804302e6b0f79a51b6e0620b03850927eee864c6d17232fe2c37868ed07f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 56ab51c35baeed4d942250986065461deec60ac9cc0c32d5d46058b26b8bc32f043e179cbcafa17daf7fe8e97028acf3975ea7a858f762319bc3ff7762a9998c
|
7
|
+
data.tar.gz: f833af2acffdb889406f0d61e6c32beef2f805044ee233a27acce888b257b2db2627dfcb42bdacabe3c579fbc7e0c775108fe105d546326f501e9bc1948abd87
|
data/.editorconfig
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# EditorConfig is awesome: http://EditorConfig.org
|
2
|
+
# top-most EditorConfig file
|
3
|
+
root = true
|
4
|
+
# all files
|
5
|
+
[*]
|
6
|
+
# Unix-style newlines
|
7
|
+
end_of_line = lf
|
8
|
+
# newline ending every file
|
9
|
+
insert_final_newline = true
|
10
|
+
# utf-8 charset
|
11
|
+
charset = utf-8
|
12
|
+
# remove and trailing whitespace chars
|
13
|
+
trim_trailing_whitespace = true
|
14
|
+
# default to space spacing
|
15
|
+
indent_style = space
|
16
|
+
# default to 2 char tabs
|
17
|
+
indent_size = 2
|
18
|
+
# no max line length
|
19
|
+
max_line_length = off
|
20
|
+
# keep curly on same line if possible
|
21
|
+
curly_bracket_next_line = false
|
22
|
+
# https://en.wikipedia.org/wiki/Indent_style#K.26R_style
|
23
|
+
indent_brace_style = K&R
|
24
|
+
# "something + something" not "something+something"
|
25
|
+
spaces_around_operators = true
|
26
|
+
# "something(true)" not "something ( true )"
|
27
|
+
spaces_around_brackets = none
|
28
|
+
[*.{js,css,scss,html,xml}]
|
29
|
+
indent_style = space
|
30
|
+
indent_size = 4
|
31
|
+
[*.{yml,sh,sh.erb}]
|
32
|
+
indent_style = space
|
33
|
+
indent_size = 2
|
34
|
+
spaces_around_operators = false
|
35
|
+
[*.{rb,rb.erb,rb.static,gemspec}]
|
36
|
+
indent_style = tab
|
37
|
+
indent_size = 4
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
roleback (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
debug (1.8.0)
|
10
|
+
irb (>= 1.5.0)
|
11
|
+
reline (>= 0.3.1)
|
12
|
+
diff-lcs (1.5.0)
|
13
|
+
io-console (0.7.1)
|
14
|
+
irb (1.6.3)
|
15
|
+
reline (>= 0.3.0)
|
16
|
+
rake (10.5.0)
|
17
|
+
reline (0.4.2)
|
18
|
+
io-console (~> 0.5)
|
19
|
+
rspec (3.12.0)
|
20
|
+
rspec-core (~> 3.12.0)
|
21
|
+
rspec-expectations (~> 3.12.0)
|
22
|
+
rspec-mocks (~> 3.12.0)
|
23
|
+
rspec-core (3.12.2)
|
24
|
+
rspec-support (~> 3.12.0)
|
25
|
+
rspec-expectations (3.12.3)
|
26
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
27
|
+
rspec-support (~> 3.12.0)
|
28
|
+
rspec-mocks (3.12.6)
|
29
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
30
|
+
rspec-support (~> 3.12.0)
|
31
|
+
rspec-support (3.12.1)
|
32
|
+
|
33
|
+
PLATFORMS
|
34
|
+
ruby
|
35
|
+
|
36
|
+
DEPENDENCIES
|
37
|
+
bundler (~> 1.17)
|
38
|
+
debug
|
39
|
+
rake (~> 10.0)
|
40
|
+
roleback!
|
41
|
+
rspec (~> 3.0)
|
42
|
+
|
43
|
+
BUNDLED WITH
|
44
|
+
1.17.2
|
data/README.md
ADDED
@@ -0,0 +1,226 @@
|
|
1
|
+
# Roleback
|
2
|
+
|
3
|
+
Roleback is a simple DSL for writing static RBAC rules for your application. Roleback is not concerned with how you store or enforce your roles, it only cares about how you define them. Storing roles against a user class is easy enough, and there are plenty of gems out there to help you enforce them, like [Pundit](https://github.com/varvet/pundit) and [CanCanCan](https://github.com/CanCanCommunity/cancancan).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'roleback'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```bash
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
```bash
|
22
|
+
$ gem install roleback
|
23
|
+
```
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
Using Roleback is simple. Define your roles in a ruby file, and then load them into your application. For example in Rails, you can create a file loaded during your application load, like `config/initializers/roles.rb`:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
# config/initializers/roles.rb
|
31
|
+
Roleback.define do
|
32
|
+
role :admin do
|
33
|
+
can :manage
|
34
|
+
end
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
Roleback defines permissions on by roles. In the example above, we've defined a role called `admin` that can `manage` anything. Usually permissions are defined with three pieces of information: `scope`, `resource` and `action`.
|
39
|
+
|
40
|
+
`resource` is the object you want to check permissions against. `action` is the action you want to check permissions for. For example, you might want to check `read` action, on a blog `post`:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
# config/initializers/roles.rb
|
44
|
+
Roleback.define do
|
45
|
+
role :admin do
|
46
|
+
resource :post do
|
47
|
+
can :read
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
`resource` however, always includes 7 more actions: `create`, `read`, `update`, `delete`, `list`, `edit` and `new` to make it easier to define permissions for common actions. You can change this behavior using the `only` and `except` options:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
# config/initializers/roles.rb
|
57
|
+
Roleback.define do
|
58
|
+
role :admin do
|
59
|
+
resource :post, only: [:read, :create, :update, :delete] do
|
60
|
+
can :read
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
`scope` adds context to your permissions. For example, you might want to grant `read` on a `post` in the web, but not in other contexts (like an API):
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
# config/initializers/roles.rb
|
70
|
+
Roleback.define do
|
71
|
+
role :admin do
|
72
|
+
scope :web do
|
73
|
+
resource :post do
|
74
|
+
can :read
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
## Grant and Deny
|
82
|
+
Permissions are granted using `can` and denied using `cannot`:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
# config/initializers/roles.rb
|
86
|
+
Roleback.define do
|
87
|
+
role :admin do
|
88
|
+
scope :web do
|
89
|
+
resource :post do
|
90
|
+
can :read
|
91
|
+
cannot :write
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
By default, all `resource` default permissions (create, read, update, delete, list, edit and new) are granted (ie `can`).
|
99
|
+
|
100
|
+
## Inheritance
|
101
|
+
Roles can inherit from other roles:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
# config/initializers/roles.rb
|
105
|
+
Roleback.define do
|
106
|
+
role :admin do
|
107
|
+
can :manage
|
108
|
+
end
|
109
|
+
|
110
|
+
role :editor, inherits_from: :admin do
|
111
|
+
cannot :delete
|
112
|
+
end
|
113
|
+
end
|
114
|
+
```
|
115
|
+
|
116
|
+
Roles can also inherit from multiple roles:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
# config/initializers/roles.rb
|
120
|
+
Roleback.define do
|
121
|
+
role :admin do
|
122
|
+
can :manage
|
123
|
+
end
|
124
|
+
|
125
|
+
role :author do
|
126
|
+
can :write
|
127
|
+
end
|
128
|
+
|
129
|
+
role :editor, inherits_from: [:admin, :author] do
|
130
|
+
cannot :delete
|
131
|
+
end
|
132
|
+
end
|
133
|
+
```
|
134
|
+
|
135
|
+
While you don't need to define a parent role before the child role, circular dependencies are not allowed, within the same parental line. For example, if `:moderator` inherits from `:admin`, `:admin` cannot inherit from `:moderator`, directly or indirectly. However, when inheriting from multiple parents, circular dependencies are allowed, as long as they are not in the same parental line. For example, `:editor` can inherit from `:admin` and `:author`, and `:author` can inherit from `:editor`, as long as `:editor` does not inherit from `:author` directly or indirectly.
|
136
|
+
|
137
|
+
When it comes to consolidating the rules of inherited roles, Roleback allows repeated rules as long as they don't belong to the same role. For example, it is not allowed to define a rule twice, even with the same outcome:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
# config/initializers/roles.rb
|
141
|
+
Roleback.define do
|
142
|
+
role :admin do
|
143
|
+
can :manage
|
144
|
+
can :manage # <- not allowed
|
145
|
+
end
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
You can however, define the same rule in different roles, as long as they don't contradict each other:
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
# config/initializers/roles.rb
|
153
|
+
Roleback.define do
|
154
|
+
role :admin do
|
155
|
+
can :manage
|
156
|
+
end
|
157
|
+
|
158
|
+
role :editor, inherits_from: :admin do
|
159
|
+
can :manage
|
160
|
+
end
|
161
|
+
end
|
162
|
+
```
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
# config/initializers/roles.rb
|
166
|
+
Roleback.define do
|
167
|
+
role :admin do
|
168
|
+
can :manage
|
169
|
+
end
|
170
|
+
|
171
|
+
role :editor, inherits_from: :admin do
|
172
|
+
cannot :manage # <- not allowed
|
173
|
+
end
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
## Checking Permissions
|
178
|
+
Roleback doesn't care how you check permissions, but it does provide a simple API for doing so:
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
Roleback.can?(:admin, resource: :post, action: :read) # => true
|
182
|
+
Roleback.can?(:editor, resource: :post, :delete) # => false
|
183
|
+
```
|
184
|
+
|
185
|
+
After the definition of roles is finished (`Roleback.define`), all each role, ends up with the collection of all rules it has plus all the rules it has inherited from other roles. These rules are used to check for permissions. When the `can?` method is called with `scope`, `resource` and `action`, `can?` will return the outcome of the most specific rule that matches the given `scope`, `resource` and `action`. If no rule matches, `can?` will return `false`. If you have both `can` and `cannot` rules for a check, `cannot` will take precedence (deny over grant).
|
186
|
+
|
187
|
+
### `User` class
|
188
|
+
If you have a `User` class, Roleback will automatically, add a `can?` method to it:
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
user = User.find(1)
|
192
|
+
user.can?(:admin, resource: :post, action: :read) # => true
|
193
|
+
user.can?(:editor, resource: :post, :delete) # => false
|
194
|
+
```
|
195
|
+
|
196
|
+
Your `User` class has to have a method called `roles` that returns an array of role names as symbols.
|
197
|
+
|
198
|
+
The `User` class returns an array of roles, then Roleback will check each role for a match and will return `true` (grant) when the first role matches. If no role matches, `can?` will return `false`. This is an important point to remember when using class extension, which basically means if you grant the user multiple rules, it will return `true` if any of the rules match, even you have rules that deny access to the same resource and action.
|
199
|
+
|
200
|
+
You can change the class to be extended from `User`, using `user_class` option in `define`:
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
Roleback.define(user_class: Admin) do
|
204
|
+
# ...
|
205
|
+
end
|
206
|
+
```
|
207
|
+
|
208
|
+
If you don't want to extend your `User` class, pass in `nil` as the `user_class` option:
|
209
|
+
|
210
|
+
```ruby
|
211
|
+
Roleback.define(user_class: nil) do
|
212
|
+
# ...
|
213
|
+
end
|
214
|
+
```
|
215
|
+
|
216
|
+
## Recommendations
|
217
|
+
|
218
|
+
Even though Roleback doesn't impose any opinions on how define your rules (sacrilegious in Rails world, I know), here are some recommendations that might help using it with more ease:
|
219
|
+
|
220
|
+
1. Although Roleback, support deny permissions (`cannot`), I recommend against using those and always define your rules with grant permissions (`can`). This will make it easier to reason about your rules and will make it easier to debug them.
|
221
|
+
2. Either map your roles to actual organizational roles (marketing, support, etc), or define them based on their access context (commenter, editor, etc). Don't mix the two. Use multiple inheritance when defining the roles based on access context and use single inheritance when defining them based on organizational roles.
|
222
|
+
3. Define your roles in a single file, and load them during application load. (`config/initializers/roles.rb` in Rails is a good place).
|
223
|
+
|
224
|
+
## Contributing
|
225
|
+
|
226
|
+
Bug reports and pull requests are welcome on this GitHub repository. PRs are welcome and more likely to be accepted if they include tests.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "roleback"
|
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(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
module Roleback
|
2
|
+
def self.define(options = {}, &block)
|
3
|
+
@config = ::Roleback::Configuration::Builder.new(options, &block).build if block_given?
|
4
|
+
@config.construct!
|
5
|
+
|
6
|
+
# is there a ::User class defined?
|
7
|
+
if options[:user_class]
|
8
|
+
@user_class = options[:user_class]
|
9
|
+
elsif defined?(::User)
|
10
|
+
@user_class = ::User
|
11
|
+
else
|
12
|
+
@user_class = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
# extend the user class
|
16
|
+
::Roleback::UserExtension.extend!(@user_class) if @user_class
|
17
|
+
|
18
|
+
@config
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.configuration
|
22
|
+
@config || (raise ::Roleback::NotConfiguredError)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.any
|
26
|
+
::Roleback::ANY
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.allow
|
30
|
+
::Roleback::ALLOW
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.deny
|
34
|
+
::Roleback::DENY
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.clear!
|
38
|
+
@config = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.roles
|
42
|
+
self.configuration.roles
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.can?(role_name, resource: ::Roleback.any, scope: ::Roleback.any, action: ::Roleback.any)
|
46
|
+
role = self.configuration.find_role!(role_name)
|
47
|
+
|
48
|
+
return true if role.rules.can?(resource: resource, scope: scope, action: action)
|
49
|
+
|
50
|
+
false
|
51
|
+
end
|
52
|
+
|
53
|
+
class Configuration
|
54
|
+
attr_reader :roles
|
55
|
+
attr_reader :max_inheritance_depth
|
56
|
+
|
57
|
+
def initialize(options = {})
|
58
|
+
@options = options
|
59
|
+
@roles = {}
|
60
|
+
|
61
|
+
if options[:max_inheritance_depth]
|
62
|
+
@max_inheritance_depth = options[:max_inheritance_depth]
|
63
|
+
else
|
64
|
+
@max_inheritance_depth = 10
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def find_role!(name)
|
69
|
+
role = self.roles[name.to_sym]
|
70
|
+
raise ::Roleback::MissingRole, "Role #{name} not found" unless role
|
71
|
+
role
|
72
|
+
end
|
73
|
+
|
74
|
+
def can?(role_name, scope: ::Roleback::ANY, resource: ::Roleback::ANY, action: ::Roleback::ANY)
|
75
|
+
role = self.find_role!(role_name)
|
76
|
+
role.can?(scope: scope, resource: resource, action: action)
|
77
|
+
end
|
78
|
+
|
79
|
+
def construct!
|
80
|
+
# go through all roles and find their parents
|
81
|
+
@roles.each do |name, role|
|
82
|
+
parents = role.parents
|
83
|
+
next unless parents && !parents.empty?
|
84
|
+
|
85
|
+
found_parents = []
|
86
|
+
|
87
|
+
parents.each do |parent|
|
88
|
+
found_parent = @roles[parent]
|
89
|
+
raise ::Roleback::BadConfiguration, "Role #{parent} not found" unless found_parent
|
90
|
+
|
91
|
+
found_parents << found_parent
|
92
|
+
role.instance_variable_set(:@parents, found_parents)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# go through all roles, and inherit their parents' rules
|
97
|
+
@roles.each do |name, role|
|
98
|
+
role.inherit
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class Builder
|
103
|
+
def initialize(options = {}, &block)
|
104
|
+
@options = options
|
105
|
+
@config = ::Roleback::Configuration.new(options)
|
106
|
+
@parent = nil
|
107
|
+
|
108
|
+
instance_eval(&block) if block_given?
|
109
|
+
end
|
110
|
+
|
111
|
+
def build
|
112
|
+
@config
|
113
|
+
end
|
114
|
+
|
115
|
+
def role(name, options = {}, &block)
|
116
|
+
roles = @config.instance_variable_get(:@roles) || {}
|
117
|
+
|
118
|
+
raise ::Roleback::BadConfiguration, "Role #{name} already defined" if roles[name]
|
119
|
+
|
120
|
+
validate_options!(options)
|
121
|
+
|
122
|
+
parents = options[:inherits_from]
|
123
|
+
|
124
|
+
role = ::Roleback::Definitions::Role.new(name, parents: parents)
|
125
|
+
role.instance_eval(&block) if block_given?
|
126
|
+
|
127
|
+
roles[name] = role
|
128
|
+
@config.instance_variable_set(:@roles, roles)
|
129
|
+
|
130
|
+
role
|
131
|
+
end
|
132
|
+
|
133
|
+
def validate_options!(options)
|
134
|
+
raise ::Roleback::BadConfiguration, "Invalid options" if options.keys.any? { |k| ![:inherits_from].include?(k) }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Roleback
|
2
|
+
module Definitions
|
3
|
+
class Resource < ::Roleback::Definitions::RuleBased
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
DEFAULT_ACTION_PATH = [:create, :show, :update, :delete, :index, :new, :edit]
|
7
|
+
|
8
|
+
def initialize(name, role:, scope: ::Roleback::ANY, options: {}, &block)
|
9
|
+
@name = name
|
10
|
+
@role = role
|
11
|
+
@scope = scope
|
12
|
+
@options = options
|
13
|
+
|
14
|
+
super(role: role, resource: self, scope: scope)
|
15
|
+
|
16
|
+
validate_options!
|
17
|
+
|
18
|
+
# create rules for each action
|
19
|
+
selected_actions.each do |action|
|
20
|
+
do_rule(role: @role, resource: self, scope: @scope, action: action, outcome: ::Roleback::ALLOW)
|
21
|
+
end
|
22
|
+
|
23
|
+
instance_eval(&block) if block_given?
|
24
|
+
end
|
25
|
+
|
26
|
+
def match(resource)
|
27
|
+
to_check = resource.is_a?(::Roleback::Definitions::Resource) ? resource.name.to_s : resource.to_s
|
28
|
+
@name.to_s == to_check || @name == ::Roleback.any || resource == ::Roleback.any
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(other)
|
32
|
+
return false unless other.respond_to?(:name)
|
33
|
+
|
34
|
+
other.name == name
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def validate_options!
|
40
|
+
if @options[:only] && @options[:except]
|
41
|
+
raise ::Roleback::BadConfiguration, "You can't specify both :only and :except options"
|
42
|
+
end
|
43
|
+
|
44
|
+
if @options[:only] && !@options[:only].is_a?(Array)
|
45
|
+
raise ::Roleback::BadConfiguration, "The :only option must be an array"
|
46
|
+
end
|
47
|
+
|
48
|
+
if @options[:except] && !@options[:except].is_a?(Array)
|
49
|
+
raise ::Roleback::BadConfiguration, "The :except option must be an array"
|
50
|
+
end
|
51
|
+
|
52
|
+
if @options.keys.any? { |k| ![:only, :except].include?(k) }
|
53
|
+
raise ::Roleback::BadConfiguration, "Invalid options"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def selected_actions
|
58
|
+
if @options[:only]
|
59
|
+
return @options[:only]
|
60
|
+
elsif @options[:except]
|
61
|
+
return DEFAULT_ACTION_PATH - @options[:except]
|
62
|
+
else
|
63
|
+
return DEFAULT_ACTION_PATH
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Roleback
|
2
|
+
module Definitions
|
3
|
+
class Role < Roleback::Definitions::RuleBased
|
4
|
+
attr_reader :name
|
5
|
+
attr_reader :parents
|
6
|
+
|
7
|
+
def initialize(name, parents: nil)
|
8
|
+
@name = name
|
9
|
+
@rule_book = ::Roleback::RuleBook.new(self)
|
10
|
+
@scopes = {}
|
11
|
+
@resources = {}
|
12
|
+
|
13
|
+
if parents
|
14
|
+
if parents.is_a?(Symbol)
|
15
|
+
@parents = [parents]
|
16
|
+
elsif parents.is_a?(Array)
|
17
|
+
# check for duplicates
|
18
|
+
raise ::Roleback::BadConfiguration, "Duplicate parents found for role #{name}" if parents.uniq.length != parents.length
|
19
|
+
|
20
|
+
@parents = parents
|
21
|
+
else
|
22
|
+
raise ::Roleback::BadConfiguration, "Parent must be a symbol or an array of symbols"
|
23
|
+
end
|
24
|
+
else
|
25
|
+
@parents = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
super(role: self, resource: ::Roleback::ANY, scope: ::Roleback::ANY)
|
29
|
+
end
|
30
|
+
|
31
|
+
def rules
|
32
|
+
@rule_book
|
33
|
+
end
|
34
|
+
|
35
|
+
def keys
|
36
|
+
@rule_book.rules.keys
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_rule(rule)
|
40
|
+
@rule_book.add(rule)
|
41
|
+
end
|
42
|
+
|
43
|
+
def resource(name, options = {}, &block)
|
44
|
+
raise ::Roleback::BadConfiguration, "Resource #{name} already defined" if @resources[name]
|
45
|
+
|
46
|
+
resource = ::Roleback::Definitions::Resource.new(name, role: self, options: options, &block)
|
47
|
+
@resources[name] = resource
|
48
|
+
end
|
49
|
+
|
50
|
+
def scope(name, &block)
|
51
|
+
raise ::Roleback::BadConfiguration, "Scope #{name} already defined" if @scopes[name]
|
52
|
+
|
53
|
+
scope = ::Roleback::Definitions::Scope.new(name, role: self, &block)
|
54
|
+
@scopes[name] = scope
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
self.name.to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
def inherit
|
62
|
+
return if @parents.nil?
|
63
|
+
|
64
|
+
new_rules = do_inherit
|
65
|
+
|
66
|
+
if new_rules.empty?
|
67
|
+
# no rules to inherit
|
68
|
+
return
|
69
|
+
end
|
70
|
+
|
71
|
+
# add the new rules to the rule book
|
72
|
+
self.rules.clear_rules
|
73
|
+
new_rules.each do |rule|
|
74
|
+
self.rules.add(rule)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def do_inherit(rule_set = [], level = 0)
|
81
|
+
# don't go too deep
|
82
|
+
raise ::Roleback::BadConfiguration, "Circular dependency detected (#{level} out of maximum allowed of #{::Roleback.configuration.max_inheritance_depth})" if level > ::Roleback.configuration.max_inheritance_depth
|
83
|
+
|
84
|
+
new_rules = rule_set + self.rules.to_a
|
85
|
+
|
86
|
+
return new_rules if @parents.nil? || @parents.empty?
|
87
|
+
|
88
|
+
@parents.each do |parent|
|
89
|
+
parent_rules = parent.send(:do_inherit, rule_set, level + 1)
|
90
|
+
new_rules = new_rules + parent_rules
|
91
|
+
end
|
92
|
+
|
93
|
+
return new_rules
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Roleback
|
2
|
+
module Definitions
|
3
|
+
class RuleBased
|
4
|
+
attr_reader :role
|
5
|
+
attr_reader :resource
|
6
|
+
|
7
|
+
def initialize(role:, resource:, scope:)
|
8
|
+
@role = role
|
9
|
+
@resource = resource
|
10
|
+
@scope = scope
|
11
|
+
end
|
12
|
+
|
13
|
+
def can(action)
|
14
|
+
do_rule(role: @role, resource: @resource, scope: @scope, action: action, outcome: ::Roleback::ALLOW)
|
15
|
+
end
|
16
|
+
|
17
|
+
def cannot(action)
|
18
|
+
do_rule(role: @role, resource: @resource, scope: @scope, action: action, outcome: ::Roleback::DENY)
|
19
|
+
end
|
20
|
+
|
21
|
+
def <=>(other)
|
22
|
+
other.numerical_value <=> numerical_value
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def do_rule(role:, resource:, scope:, action:, outcome:)
|
28
|
+
rule = ::Roleback::Rule.new(
|
29
|
+
role: role,
|
30
|
+
resource: resource,
|
31
|
+
scope: scope,
|
32
|
+
action: action,
|
33
|
+
outcome: outcome)
|
34
|
+
|
35
|
+
role.add_rule(rule)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Roleback
|
2
|
+
module Definitions
|
3
|
+
class Scope < ::Roleback::Definitions::RuleBased
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
def initialize(name, role:, options: {}, &block)
|
7
|
+
@name = name
|
8
|
+
@role = role
|
9
|
+
@options = options
|
10
|
+
|
11
|
+
super(role: role, scope: self, resource: ::Roleback::ANY)
|
12
|
+
|
13
|
+
instance_eval(&block) if block_given?
|
14
|
+
end
|
15
|
+
|
16
|
+
def resource(name, options = {}, &block)
|
17
|
+
::Roleback::Definitions::Resource.new(name, role: @role, scope: self, options: options, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def match(scope)
|
21
|
+
to_check = scope.is_a?(::Roleback::Definitions::Scope) ? scope.name.to_s : scope.to_s
|
22
|
+
@name.to_s == scope.name.to_s || @name == ::Roleback.any || scope == ::Roleback.any
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
return false unless other.respond_to?(:name)
|
27
|
+
|
28
|
+
other.name == name
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Roleback
|
2
|
+
class OutcomeBase
|
3
|
+
attr_reader :outcome
|
4
|
+
|
5
|
+
def initialize(outcome)
|
6
|
+
@outcome = outcome
|
7
|
+
end
|
8
|
+
|
9
|
+
def allowed?
|
10
|
+
@outcome == :allow
|
11
|
+
end
|
12
|
+
|
13
|
+
def denied?
|
14
|
+
@outcome == :deny
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
@outcome.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(other)
|
22
|
+
return false unless other.respond_to?(:outcome)
|
23
|
+
|
24
|
+
other.outcome == outcome
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Allow < OutcomeBase
|
29
|
+
def initialize
|
30
|
+
super(:allow)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Deny < OutcomeBase
|
35
|
+
def initialize
|
36
|
+
super(:deny)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Any
|
41
|
+
def name
|
42
|
+
:'*'
|
43
|
+
end
|
44
|
+
|
45
|
+
def ==(other)
|
46
|
+
return true if other.is_a?(Any)
|
47
|
+
return false unless other.respond_to?(:name)
|
48
|
+
|
49
|
+
other.name.to_s == name.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
def match(scope)
|
53
|
+
true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
ANY = Any.new
|
58
|
+
|
59
|
+
ALLOW = Allow.new
|
60
|
+
DENY = Deny.new
|
61
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Roleback
|
2
|
+
class Rule
|
3
|
+
attr_reader :role
|
4
|
+
attr_reader :resource
|
5
|
+
attr_reader :scope
|
6
|
+
attr_reader :action
|
7
|
+
attr_reader :outcome
|
8
|
+
|
9
|
+
def initialize(role:, resource:, scope:, action:, outcome:)
|
10
|
+
@role = role
|
11
|
+
@resource = resource
|
12
|
+
@scope = scope
|
13
|
+
@action = action
|
14
|
+
@outcome = outcome
|
15
|
+
end
|
16
|
+
|
17
|
+
def key
|
18
|
+
"#{@scope.name}:/#{@resource.name}/#{@action}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"#{key}->#{@outcome}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def match(resource:, scope:, action:)
|
26
|
+
if @resource.match(resource) && @scope.match(scope)
|
27
|
+
if @action == ::Roleback.any || @action.to_s == action.to_s
|
28
|
+
return self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# two rules are conflicting, when the have the same scope, resource and action, but different outcomes
|
36
|
+
def conflicts_with?(rule)
|
37
|
+
# if the rules are the same, they don't conflict
|
38
|
+
return false if self == rule
|
39
|
+
|
40
|
+
# if the scope, resource and action are the same, but the outcome is different, they conflict
|
41
|
+
return true if @scope.name == rule.scope.name && @resource.name == rule.resource.name && @action == rule.action && @outcome.outcome != rule.outcome.outcome
|
42
|
+
|
43
|
+
# otherwise, they don't conflict
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
# calculate a numerical value for this rule to be used for sorting
|
48
|
+
# the value is the sum of the following:
|
49
|
+
# (scope_value * scope_weight) + (resource_value * resource_weight) * (outcome_value * outcome_weight)
|
50
|
+
# scope_weight = 100
|
51
|
+
# resource_weight = 10
|
52
|
+
# outcome_weight = 1
|
53
|
+
# scope_value = 0 if scope == ANY, 1 otherwise
|
54
|
+
# resource_value = 0 if resource == ANY, 1 otherwise
|
55
|
+
# outcome_value = 0 if outcome == ALLOW, 1 otherwise
|
56
|
+
def numerical_value
|
57
|
+
scope_value = @scope == ::Roleback::ANY ? 0 : 1
|
58
|
+
resource_value = @resource == ::Roleback::ANY ? 0 : 1
|
59
|
+
outcome_value = @outcome == ::Roleback::ALLOW ? 0 : 1
|
60
|
+
|
61
|
+
(scope_value * 100) + (resource_value * 10) + (outcome_value * 1)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Roleback
|
2
|
+
class RuleBook
|
3
|
+
attr_reader :rules
|
4
|
+
|
5
|
+
def initialize(role)
|
6
|
+
@rules = {}
|
7
|
+
@role = role
|
8
|
+
end
|
9
|
+
|
10
|
+
def add(rule)
|
11
|
+
raise ::Roleback::BadConfiguration, "Adding a rule with no role" unless rule.role
|
12
|
+
|
13
|
+
if @rules[rule.key]
|
14
|
+
if @rules[rule.key].outcome.outcome == rule.outcome.outcome
|
15
|
+
# don't allow it if they share a rulebook
|
16
|
+
if @role.name == rule.role.name
|
17
|
+
raise ::Roleback::BadConfiguration, "Rule #{rule.key} already defined"
|
18
|
+
else
|
19
|
+
# this duplicate is through inheritance, so we can safely ignore it
|
20
|
+
return
|
21
|
+
end
|
22
|
+
else
|
23
|
+
raise ::Roleback::BadConfiguration, "Rule #{rule.key} already defined with a different outcome (conflicting rules)"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# detect conflicting rules
|
28
|
+
@rules.each do |key, existing_rule|
|
29
|
+
if existing_rule.conflicts_with?(rule)
|
30
|
+
raise ::Roleback::BadConfiguration, "Rule #{rule.key} conflicts with #{existing_rule.key}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
@rules[rule.key] = rule
|
35
|
+
end
|
36
|
+
|
37
|
+
def clear_rules
|
38
|
+
@rules = {}
|
39
|
+
end
|
40
|
+
|
41
|
+
def length
|
42
|
+
@rules.length
|
43
|
+
end
|
44
|
+
|
45
|
+
def [](key)
|
46
|
+
@rules[key]
|
47
|
+
end
|
48
|
+
|
49
|
+
def keys
|
50
|
+
@rules.keys
|
51
|
+
end
|
52
|
+
|
53
|
+
def match_all(resource:, scope:, action:)
|
54
|
+
result = []
|
55
|
+
@rules.each do |key, rule|
|
56
|
+
result << rule if rule.match(resource: resource, scope: scope, action: action)
|
57
|
+
end
|
58
|
+
|
59
|
+
result
|
60
|
+
end
|
61
|
+
|
62
|
+
def can?(resource:, scope:, action:)
|
63
|
+
# get all rules that matches the given resource, scope and action
|
64
|
+
rules = match_all(resource: resource, scope: scope, action: action)
|
65
|
+
|
66
|
+
# if there are no rules, return false
|
67
|
+
return false if rules.empty?
|
68
|
+
|
69
|
+
# create a rule book with the matching rules
|
70
|
+
match_book = self.class.new(@role)
|
71
|
+
rules.each do |rule|
|
72
|
+
match_book.add(rule)
|
73
|
+
end
|
74
|
+
|
75
|
+
# sort the rules
|
76
|
+
sorted_rules = self.class.sort(match_book.rules)
|
77
|
+
|
78
|
+
# iterate over the sorted rules and find the first rule that matches
|
79
|
+
sorted_rules.each do |rule|
|
80
|
+
if rule.match(resource: resource, scope: scope, action: action)
|
81
|
+
return rule.outcome.allowed?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# if no rule matches, return false
|
86
|
+
return false
|
87
|
+
end
|
88
|
+
|
89
|
+
# sorts the rules, based on the rules' numerical value
|
90
|
+
def self.sort(rules)
|
91
|
+
# rules should be a hash
|
92
|
+
raise ::ArgumentError, "rules should be a hash but it is a #{rules.class}" unless rules.is_a?(Hash)
|
93
|
+
|
94
|
+
# rules is a hash, so we need to convert it to an array
|
95
|
+
rules = rules.values
|
96
|
+
|
97
|
+
# sort the rules
|
98
|
+
rules.sort! do |a, b|
|
99
|
+
b.numerical_value <=> a.numerical_value
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def sort!
|
104
|
+
@rules = self.class.sort(@rules)
|
105
|
+
end
|
106
|
+
|
107
|
+
def sort
|
108
|
+
self.class.sort(@rules)
|
109
|
+
end
|
110
|
+
|
111
|
+
def to_s
|
112
|
+
@rules.values.map(&:to_s).join("\n")
|
113
|
+
end
|
114
|
+
|
115
|
+
def to_a
|
116
|
+
@rules.values
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Roleback
|
2
|
+
class UserExtension
|
3
|
+
def self.extend!(user_class)
|
4
|
+
user_instance = user_class.new
|
5
|
+
unless user_instance.respond_to?(:roles) && user_instance.method(:roles).arity == 0
|
6
|
+
raise ::Roleback::InvalidOrMisconfiguredUserClass, "User class #{user_class.name} should have a method call roles that returns an array of role names"
|
7
|
+
end
|
8
|
+
|
9
|
+
if user_class.instance_methods.include?(:can?)
|
10
|
+
raise ::Roleback::InvalidOrMisconfiguredUserClass, "User class #{user_class.name} already has a method called can?"
|
11
|
+
end
|
12
|
+
|
13
|
+
user_class.class_eval do
|
14
|
+
def can?(resource: Roleback.any, scope: Roleback.any, action: Roleback.any)
|
15
|
+
# get all user roles
|
16
|
+
roles = self.roles
|
17
|
+
return false if roles.empty?
|
18
|
+
|
19
|
+
if !roles.is_a?(Array)
|
20
|
+
raise ::Roleback::InvalidOrMisconfiguredUserClass, "User class #{self.class}#roles should return an array of role names"
|
21
|
+
end
|
22
|
+
|
23
|
+
roles.each do |role|
|
24
|
+
return true if ::Roleback.can?(role.to_sym, resource: resource, scope: scope, action: action)
|
25
|
+
end
|
26
|
+
|
27
|
+
# no role can perform the action on the resource
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/roleback.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "roleback/version"
|
2
|
+
require 'roleback/definitions/load'
|
3
|
+
Dir["#{File.dirname(__FILE__)}/roleback/**/*.rb"].each { |f| load(f) }
|
4
|
+
|
5
|
+
module Roleback
|
6
|
+
class Error < StandardError; end
|
7
|
+
class NotConfiguredError < StandardError; end
|
8
|
+
class BadConfiguration < StandardError; end
|
9
|
+
class BadMatch < StandardError; end
|
10
|
+
class InvalidOrMisconfiguredUserClass < StandardError; end
|
11
|
+
class MissingRole < StandardError; end
|
12
|
+
end
|
data/roleback.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "roleback/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "roleback"
|
8
|
+
spec.version = Roleback::VERSION
|
9
|
+
spec.authors = ["Khash Sajadi"]
|
10
|
+
spec.email = ["khash@cloud66.com"]
|
11
|
+
|
12
|
+
spec.summary = "Roleback provides a simple DSL to define RBAC rules for your application."
|
13
|
+
spec.homepage = "https://github.com/cloud66-oss/roleback"
|
14
|
+
|
15
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
16
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
17
|
+
if spec.respond_to?(:metadata)
|
18
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
19
|
+
|
20
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
21
|
+
spec.metadata["source_code_uri"] = "https://github.com/cloud66-oss/roleback"
|
22
|
+
spec.metadata["changelog_uri"] = "https://github.com/cloud66-oss/roleback/blob/main/CHANGELOG.md"
|
23
|
+
else
|
24
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
25
|
+
end
|
26
|
+
|
27
|
+
# Specify which files should be added to the gem when it is released.
|
28
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
29
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
30
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
31
|
+
end
|
32
|
+
spec.bindir = "exe"
|
33
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
34
|
+
spec.require_paths = ["lib"]
|
35
|
+
|
36
|
+
spec.add_development_dependency "bundler", "~> 1.17"
|
37
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
38
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
39
|
+
spec.add_development_dependency "debug"
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: roleback
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Khash Sajadi
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-01-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.17'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.17'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: debug
|
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
|
+
description:
|
70
|
+
email:
|
71
|
+
- khash@cloud66.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".editorconfig"
|
77
|
+
- ".gitignore"
|
78
|
+
- ".rspec"
|
79
|
+
- ".travis.yml"
|
80
|
+
- Gemfile
|
81
|
+
- Gemfile.lock
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- bin/console
|
85
|
+
- bin/setup
|
86
|
+
- lib/roleback.rb
|
87
|
+
- lib/roleback/configuration.rb
|
88
|
+
- lib/roleback/definitions/load.rb
|
89
|
+
- lib/roleback/definitions/resource.rb
|
90
|
+
- lib/roleback/definitions/role.rb
|
91
|
+
- lib/roleback/definitions/rule_based.rb
|
92
|
+
- lib/roleback/definitions/scope.rb
|
93
|
+
- lib/roleback/outcome.rb
|
94
|
+
- lib/roleback/rule.rb
|
95
|
+
- lib/roleback/rule_book.rb
|
96
|
+
- lib/roleback/user_extension.rb
|
97
|
+
- lib/roleback/version.rb
|
98
|
+
- roleback.gemspec
|
99
|
+
homepage: https://github.com/cloud66-oss/roleback
|
100
|
+
licenses: []
|
101
|
+
metadata:
|
102
|
+
allowed_push_host: https://rubygems.org
|
103
|
+
homepage_uri: https://github.com/cloud66-oss/roleback
|
104
|
+
source_code_uri: https://github.com/cloud66-oss/roleback
|
105
|
+
changelog_uri: https://github.com/cloud66-oss/roleback/blob/main/CHANGELOG.md
|
106
|
+
post_install_message:
|
107
|
+
rdoc_options: []
|
108
|
+
require_paths:
|
109
|
+
- lib
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
requirements: []
|
121
|
+
rubygems_version: 3.0.3.1
|
122
|
+
signing_key:
|
123
|
+
specification_version: 4
|
124
|
+
summary: Roleback provides a simple DSL to define RBAC rules for your application.
|
125
|
+
test_files: []
|