permisi 0.0.1 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +53 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +9 -0
- data/CHANGELOG.md +46 -3
- data/CONTRIBUTING.md +25 -0
- data/Gemfile +5 -3
- data/Gemfile.lock +40 -3
- data/README.md +225 -16
- data/lib/generators/permisi/install_generator.rb +33 -0
- data/lib/generators/permisi/templates/initializer.rb +17 -0
- data/lib/generators/permisi/templates/migration.rb +30 -0
- data/lib/permisi.rb +30 -3
- data/lib/permisi/actable.rb +11 -0
- data/lib/permisi/backend.rb +27 -0
- data/lib/permisi/backend/active_record.rb +27 -0
- data/lib/permisi/backend/active_record/actor.rb +61 -0
- data/lib/permisi/backend/active_record/actor_role.rb +20 -0
- data/lib/permisi/backend/active_record/role.rb +41 -0
- data/lib/permisi/backend/mongoid.rb +9 -0
- data/lib/permisi/config.rb +53 -0
- data/lib/permisi/permission_util.rb +100 -0
- data/lib/permisi/version.rb +1 -1
- data/permisi.gemspec +14 -6
- metadata +86 -10
- data/.github/workflows/main.yml +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b56c15224ab819fd173b51ce3cd341836a9edcbf6d2a0c0496813826c168f4c
|
4
|
+
data.tar.gz: 1eb2ef335b4fa1a2790573ca118c9178cff6d96ef6d3334cc165650c216bbc97
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e099b2453555f7da80bfef2c4948a80523625271ba9666d2c053e455b0c42dc90d53f7f4a7233e1572696d58c7f1553dbdcaf38f9a4d6e351432a98de21d3ae
|
7
|
+
data.tar.gz: ea49a2fa692188d5864da2322a37c01de5856fb3d5bb4529966328f45e2ccc0114119c07289fa12c68afffc7f9e2030db99a10815480f180f3ea17fccd082218
|
@@ -0,0 +1,53 @@
|
|
1
|
+
name: Lint and test
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- main
|
7
|
+
tags:
|
8
|
+
- '!*'
|
9
|
+
pull_request:
|
10
|
+
paths:
|
11
|
+
- '!*.MD'
|
12
|
+
- '!*.md'
|
13
|
+
|
14
|
+
jobs:
|
15
|
+
test:
|
16
|
+
runs-on: ubuntu-latest
|
17
|
+
|
18
|
+
steps:
|
19
|
+
- uses: actions/checkout@v2
|
20
|
+
|
21
|
+
- name: Set up Ruby 2.7
|
22
|
+
uses: ruby/setup-ruby@v1
|
23
|
+
with:
|
24
|
+
ruby-version: 2.7
|
25
|
+
|
26
|
+
- name: Generate lockfile for cache key
|
27
|
+
run: bundle lock
|
28
|
+
|
29
|
+
- name: Cache gems
|
30
|
+
uses: actions/cache@v1
|
31
|
+
with:
|
32
|
+
path: vendor/bundle
|
33
|
+
key: ${{ runner.os }}-rspec-${{ hashFiles('**/Gemfile.lock') }}
|
34
|
+
restore-keys: |
|
35
|
+
${{ runner.os }}-rspec-
|
36
|
+
|
37
|
+
- name: Install gems
|
38
|
+
run: |
|
39
|
+
bundle config path vendor/bundle
|
40
|
+
bundle install --jobs 4 --retry 3
|
41
|
+
|
42
|
+
- name: Run RuboCop
|
43
|
+
uses: reviewdog/action-rubocop@v1
|
44
|
+
with:
|
45
|
+
rubocop_version: gemfile
|
46
|
+
rubocop_extensions: rubocop-rails:gemfile rubocop-rspec:gemfile
|
47
|
+
github_token: ${{ secrets.github_token }}
|
48
|
+
reporter: github-pr-check # Default is github-pr-check
|
49
|
+
|
50
|
+
- name: Run RSpec
|
51
|
+
run: bundle exec rake spec
|
52
|
+
env:
|
53
|
+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
AllCops:
|
2
2
|
TargetRubyVersion: 2.4
|
3
|
+
NewCops: enable
|
4
|
+
Exclude:
|
5
|
+
- "lib/generators/**/*"
|
6
|
+
- "spec/**/*" # stuff for later
|
7
|
+
- "lib/permisi/permission_util.rb" # The necessary evil (for now)
|
3
8
|
|
4
9
|
Style/StringLiterals:
|
5
10
|
Enabled: true
|
@@ -11,3 +16,7 @@ Style/StringLiteralsInInterpolation:
|
|
11
16
|
|
12
17
|
Layout/LineLength:
|
13
18
|
Max: 120
|
19
|
+
|
20
|
+
# Stuff for later
|
21
|
+
Documentation:
|
22
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,47 @@
|
|
1
|
-
|
2
|
-
=====
|
1
|
+
# Changelog
|
3
2
|
|
4
|
-
|
3
|
+
# 0.1.4
|
4
|
+
|
5
|
+
[_View the docs._](https://github.com/ukazap/permisi/blob/v0.1.4/README.md)
|
6
|
+
|
7
|
+
- Add actor-role uniqueness constraint (previously it was possible to append the same role to an actor many times), if you are upgrading from previous versions, please create the following migration: `add_index :permisi_actor_roles, [:actor_id, :role_id], unique: true`
|
8
|
+
- Show warning when calling "roles.delete" because it won't invalidate the cache
|
9
|
+
|
10
|
+
# 0.1.3
|
11
|
+
|
12
|
+
[_View the docs._](https://github.com/ukazap/permisi/blob/v0.1.3/README.md)
|
13
|
+
|
14
|
+
- Correct grammars and examples in the docs
|
15
|
+
- Change actor permissions cache key
|
16
|
+
|
17
|
+
# 0.1.2
|
18
|
+
|
19
|
+
[_View the docs._](https://github.com/ukazap/permisi/blob/v0.1.2/README.md)
|
20
|
+
|
21
|
+
- Fix namespaces/actions should no longer contain periods
|
22
|
+
- Implement cache config for faster access to actor permissions
|
23
|
+
|
24
|
+
# 0.1.1
|
25
|
+
|
26
|
+
[_View the docs._](https://github.com/ukazap/permisi/blob/v0.1.1/README.md)
|
27
|
+
|
28
|
+
- General code refactoring
|
29
|
+
- Improvements on ActiveRecord backend:
|
30
|
+
- Code refactoring
|
31
|
+
- Implement cache invalidation
|
32
|
+
|
33
|
+
# 0.1.0
|
34
|
+
|
35
|
+
[_View the docs._](https://github.com/ukazap/permisi/blob/v0.1.0/README.md)
|
36
|
+
|
37
|
+
Finished extraction work from my past projects.
|
38
|
+
|
39
|
+
- Implement ActiveRecord backend
|
40
|
+
- Implement `Actable` mixin
|
41
|
+
- Implement permissions hash sanitization and checking
|
42
|
+
|
43
|
+
# 0.0.1
|
44
|
+
|
45
|
+
[_View the docs._](https://github.com/ukazap/permisi/blob/v0.0.1/README.md)
|
46
|
+
|
47
|
+
Reserved the gem name: https://en.wiktionary.org/wiki/permisi
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# How to contribute
|
2
|
+
|
3
|
+
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/ukazap/permisi/blob/master/CODE_OF_CONDUCT.md).
|
4
|
+
|
5
|
+
## Found a bug?
|
6
|
+
|
7
|
+
- Search the [issues labeled "bug"](https://github.com/ukazap/permisi/issues?q=is%3Aissue+label%3Abug) to see if it's already reported.
|
8
|
+
- Make sure you are using the latest version of Permisi [![Gem Version](https://badge.fury.io/rb/permisi.svg)](https://badge.fury.io/rb/permisi)
|
9
|
+
- If you are still having an issue, create an issue including:
|
10
|
+
- Ruby version
|
11
|
+
- Gemfile.lock contents or at least major gem versions, such as Rails version
|
12
|
+
- Steps to reproduce the issue
|
13
|
+
- Full backtrace for any errors encountered
|
14
|
+
|
15
|
+
## Submitting changes
|
16
|
+
|
17
|
+
If you want to contribute an enhancement or a fix:
|
18
|
+
|
19
|
+
- Fork the project on GitHub
|
20
|
+
- After checking out the repo, run `bin/setup` to install dependencies
|
21
|
+
- Make your changes with tests
|
22
|
+
- Run `bundle exec rubocop -A` to auto-format your code
|
23
|
+
- Run `rake spec` to run the tests
|
24
|
+
- Commit the changes without making changes to the Rakefile or any other files that aren't related to your enhancement or fix
|
25
|
+
- Send a pull request
|
data/Gemfile
CHANGED
@@ -5,8 +5,10 @@ source "https://rubygems.org"
|
|
5
5
|
# Specify your gem's dependencies in permisi.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
+
gem "byebug", "~> 11.1"
|
9
|
+
gem "codecov", "~> 0.4.3"
|
8
10
|
gem "rake", "~> 13.0"
|
9
|
-
|
10
11
|
gem "rspec", "~> 3.0"
|
11
|
-
|
12
|
-
gem "
|
12
|
+
gem "rubocop", "~> 1.9"
|
13
|
+
gem "simplecov", "~> 0.21.2"
|
14
|
+
gem "sqlite3", "~> 1.4"
|
data/Gemfile.lock
CHANGED
@@ -1,13 +1,36 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
permisi (0.
|
4
|
+
permisi (0.1.4)
|
5
|
+
activemodel (>= 3.2.0)
|
6
|
+
activerecord (>= 3.2.0)
|
7
|
+
activesupport (>= 3.2.0)
|
8
|
+
zeitwerk (~> 2.4, >= 2.4.2)
|
5
9
|
|
6
10
|
GEM
|
7
11
|
remote: https://rubygems.org/
|
8
12
|
specs:
|
13
|
+
activemodel (6.1.3)
|
14
|
+
activesupport (= 6.1.3)
|
15
|
+
activerecord (6.1.3)
|
16
|
+
activemodel (= 6.1.3)
|
17
|
+
activesupport (= 6.1.3)
|
18
|
+
activesupport (6.1.3)
|
19
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
20
|
+
i18n (>= 1.6, < 2)
|
21
|
+
minitest (>= 5.1)
|
22
|
+
tzinfo (~> 2.0)
|
23
|
+
zeitwerk (~> 2.3)
|
9
24
|
ast (2.4.2)
|
25
|
+
byebug (11.1.3)
|
26
|
+
codecov (0.4.3)
|
27
|
+
simplecov (>= 0.15, < 0.22)
|
28
|
+
concurrent-ruby (1.1.8)
|
10
29
|
diff-lcs (1.4.4)
|
30
|
+
docile (1.3.5)
|
31
|
+
i18n (1.8.9)
|
32
|
+
concurrent-ruby (~> 1.0)
|
33
|
+
minitest (5.14.3)
|
11
34
|
parallel (1.20.1)
|
12
35
|
parser (3.0.0.0)
|
13
36
|
ast (~> 2.4.1)
|
@@ -28,7 +51,7 @@ GEM
|
|
28
51
|
diff-lcs (>= 1.2.0, < 2.0)
|
29
52
|
rspec-support (~> 3.10.0)
|
30
53
|
rspec-support (3.10.2)
|
31
|
-
rubocop (1.
|
54
|
+
rubocop (1.10.0)
|
32
55
|
parallel (~> 1.10)
|
33
56
|
parser (>= 3.0.0.0)
|
34
57
|
rainbow (>= 2.2.2, < 4.0)
|
@@ -40,16 +63,30 @@ GEM
|
|
40
63
|
rubocop-ast (1.4.1)
|
41
64
|
parser (>= 2.7.1.5)
|
42
65
|
ruby-progressbar (1.11.0)
|
66
|
+
simplecov (0.21.2)
|
67
|
+
docile (~> 1.1)
|
68
|
+
simplecov-html (~> 0.11)
|
69
|
+
simplecov_json_formatter (~> 0.1)
|
70
|
+
simplecov-html (0.12.3)
|
71
|
+
simplecov_json_formatter (0.1.2)
|
72
|
+
sqlite3 (1.4.2)
|
73
|
+
tzinfo (2.0.4)
|
74
|
+
concurrent-ruby (~> 1.0)
|
43
75
|
unicode-display_width (2.0.0)
|
76
|
+
zeitwerk (2.4.2)
|
44
77
|
|
45
78
|
PLATFORMS
|
46
79
|
x86_64-linux
|
47
80
|
|
48
81
|
DEPENDENCIES
|
82
|
+
byebug (~> 11.1)
|
83
|
+
codecov (~> 0.4.3)
|
49
84
|
permisi!
|
50
85
|
rake (~> 13.0)
|
51
86
|
rspec (~> 3.0)
|
52
|
-
rubocop (~> 1.
|
87
|
+
rubocop (~> 1.9)
|
88
|
+
simplecov (~> 0.21.2)
|
89
|
+
sqlite3 (~> 1.4)
|
53
90
|
|
54
91
|
BUNDLED WITH
|
55
92
|
2.2.5
|
data/README.md
CHANGED
@@ -1,10 +1,46 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
1
|
+
If you're viewing this at https://github.com/ukazap/permisi, you're reading the documentation for the main branch. [Go to specific version.](https://github.com/ukazap/permisi/blob/main/CHANGELOG.md)
|
2
|
+
|
3
|
+
<table>
|
4
|
+
<tr>
|
5
|
+
<th>
|
6
|
+
<a href="https://commons.wikimedia.org/wiki/File:Female_Chinese_Lion_Statue.jpg">
|
7
|
+
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/Female_Chinese_Lion_Statue.jpg/102px-Female_Chinese_Lion_Statue.jpg">
|
8
|
+
</a>
|
9
|
+
</th>
|
10
|
+
<th>
|
11
|
+
<h1>Permisi</h1>
|
12
|
+
<p><em>Simple and dynamic role-based access control for Rails</em></p>
|
13
|
+
<p>
|
14
|
+
<a href="https://badge.fury.io/rb/permisi">
|
15
|
+
<img src="https://badge.fury.io/rb/permisi.svg" alt="Gem Version">
|
16
|
+
</a>
|
17
|
+
<a href="https://codeclimate.com/github/ukazap/permisi/maintainability">
|
18
|
+
<img src="https://api.codeclimate.com/v1/badges/0b1238302f2012b20740/maintainability" />
|
19
|
+
</a>
|
20
|
+
<a href="https://codecov.io/gh/ukazap/permisi">
|
21
|
+
<img src="https://codecov.io/gh/ukazap/permisi/branch/main/graph/badge.svg?token=9YRMVFCDA8"/>
|
22
|
+
</a>
|
23
|
+
</p>
|
24
|
+
</th>
|
25
|
+
<th>
|
26
|
+
<a href="https://commons.wikimedia.org/wiki/File:Male_Chinese_Lion_Statue.jpg">
|
27
|
+
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/14/Male_Chinese_Lion_Statue.jpg/98px-Male_Chinese_Lion_Statue.jpg">
|
28
|
+
</a>
|
29
|
+
</th>
|
30
|
+
</tr>
|
31
|
+
</table>
|
32
|
+
|
33
|
+
## Concept
|
34
|
+
|
35
|
+
Permisi provides a way of dynamically declaring user rights (a.k.a. permissions) using a simple role-based access control scheme.
|
36
|
+
|
37
|
+
This is not an alternative to CanCanCan/Pundit, instead it complement them with dynamic role definition and role membership.
|
38
|
+
|
39
|
+
Permisi has three basic concepts:
|
40
|
+
|
41
|
+
- Actor: a person, group of people, or an automated agent who interacts with the app
|
42
|
+
- Role: a job function, job title, or rank which determines an actor's authority
|
43
|
+
- Permission: the ability to perform an action
|
8
44
|
|
9
45
|
## Installation
|
10
46
|
|
@@ -17,24 +53,197 @@ gem 'permisi'
|
|
17
53
|
And then execute:
|
18
54
|
|
19
55
|
$ bundle install
|
56
|
+
$ rails g permisi:install
|
57
|
+
|
58
|
+
## Configuring backend
|
59
|
+
|
60
|
+
Set `config.backend` in the initializer to the backend of choice for storing and retrieving roles:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
# config/initializers/permisi.rb
|
64
|
+
|
65
|
+
Permisi.init do |config|
|
66
|
+
#...
|
67
|
+
config.backend = :active_record
|
68
|
+
#...
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
To use `:active_record`, run the generated migration from the installation step:
|
73
|
+
|
74
|
+
$ rails db:migrate
|
75
|
+
|
76
|
+
Permisi only support `:active_record` backend at the moment. In the future, it will be possible to use `:mongoid`.
|
77
|
+
|
78
|
+
## Configuring permissions
|
79
|
+
|
80
|
+
First you have to predefine the permissions, which is basically a set of possible actions according to the app's use cases. The actions can be grouped in any way possible. For example, you might want to define actions around resource types.
|
20
81
|
|
21
|
-
|
82
|
+
To define the available actions in the system, assign a hash to the `config.permissions` with the following format:
|
22
83
|
|
23
|
-
|
84
|
+
```ruby
|
85
|
+
# config/initializers/permisi.rb
|
86
|
+
Permisi.init do |config|
|
87
|
+
# ...
|
88
|
+
config.permissions = {
|
89
|
+
# A symbol-array pair denotes a namespace.
|
90
|
+
# A common use of namespacing is for grouping
|
91
|
+
# available actions by resources.
|
92
|
+
authors: [
|
93
|
+
# Enclosed in the array are symbols
|
94
|
+
# denoting available actions in the namespace:
|
95
|
+
:list,
|
96
|
+
:view,
|
97
|
+
:create,
|
98
|
+
:edit,
|
99
|
+
:delete
|
100
|
+
],
|
101
|
+
# You can also use the simplified %i[] notation:
|
102
|
+
publishers: %i[list view create edit delete],
|
103
|
+
# Besides actions, you can also have nested
|
104
|
+
# namespaces:
|
105
|
+
books: [
|
106
|
+
:list,
|
107
|
+
:view,
|
108
|
+
:create,
|
109
|
+
:edit,
|
110
|
+
:delete,
|
111
|
+
{
|
112
|
+
editions: [
|
113
|
+
:list, :view, :create, :edit, :delete, :archive
|
114
|
+
]
|
115
|
+
}
|
116
|
+
]
|
117
|
+
}
|
118
|
+
# ...
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
## Defining and managing roles
|
123
|
+
|
124
|
+
Once you have the predefined permissions, you can then define different roles with different level of access within the boundary of the predefined permissions. You can delete or create new roles according to organizational changes. You can also modify existing roles without a change in your code.
|
125
|
+
|
126
|
+
You can create, edit, and destroy roles at runtime. You might also want to define preset roles via `db/seeds.rb`.
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
# Interact with Permisi.roles as you would with ActiveRecord query interfaces:
|
130
|
+
|
131
|
+
# List all roles
|
132
|
+
Permisi.roles.all
|
133
|
+
|
134
|
+
# Create a new role
|
135
|
+
admin_role = Permisi.roles.create(slug: :admin, name: "Administrator", permissions: {
|
136
|
+
books: {
|
137
|
+
list: true,
|
138
|
+
view: true,
|
139
|
+
create: true,
|
140
|
+
edit: true
|
141
|
+
}
|
142
|
+
})
|
143
|
+
|
144
|
+
# Ask specific role permission
|
145
|
+
admin_role.allows?("books.delete") # == false
|
146
|
+
|
147
|
+
# Update existing role
|
148
|
+
admin_role.permissions[:books].merge!({ delete: true })
|
149
|
+
admin_role.save
|
150
|
+
admin_role.allows?("books.delete") # == true
|
151
|
+
```
|
152
|
+
|
153
|
+
## Configuring actors
|
154
|
+
|
155
|
+
You can then give or take multiple roles to an actor which will allow or prevent them to perform certain actions in a flexible manner. But before you can do that, you have to wire up your user model with Permisi using via `Permisi::Actable` mixin.
|
156
|
+
|
157
|
+
Permisi does not hold an assumption that a specific model is present (e.g. User model). Instead, it keeps track of "actors" internally. The goal is to support multiple use cases such as actor polymorphism, user _groups_, etc.
|
158
|
+
|
159
|
+
For example, you can map your user model to Permisi's actor model like so:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
# app/models/user.rb
|
24
163
|
|
25
|
-
|
164
|
+
class User < ApplicationRecord
|
165
|
+
include Permisi::Actable
|
166
|
+
end
|
167
|
+
```
|
168
|
+
|
169
|
+
You can then interact using `#permisi` method:
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
user = User.find_by_email "esther@example.com"
|
173
|
+
user.permisi # => instance of Actor
|
174
|
+
|
175
|
+
admin_role = Permisi.roles.find_by_slug(:admin)
|
176
|
+
admin_role.allows?("books.delete") # == true
|
177
|
+
|
178
|
+
user.permisi.roles << admin_role
|
179
|
+
|
180
|
+
user.permisi.role?(:admin) # == true
|
181
|
+
user.permisi.has_role?(:admin) # == user.permisi.role? :admin
|
182
|
+
|
183
|
+
user.permisi.may_i?("books.delete") # == true
|
184
|
+
user.permisi.may?("books.delete") # == user.permisi.may_i? "books.delete"
|
185
|
+
|
186
|
+
user.permisi.roles.destroy(admin_role)
|
187
|
+
|
188
|
+
user.permisi.role?(:admin) # == false
|
189
|
+
user.permisi.may_i?("books.delete") # == false
|
190
|
+
```
|
191
|
+
|
192
|
+
## Caching
|
193
|
+
|
194
|
+
Permisi has several optimizations out of the box: actor roles eager loading, actor permissions memoization, and the optional actor permissions caching.
|
195
|
+
|
196
|
+
### Actor roles eager loading
|
197
|
+
|
198
|
+
Although checking whether an actor has a role goes against a good RBAC practice, it is still possible on Permisi. Calling `role?` multiple times will only make one call to the database:
|
199
|
+
|
200
|
+
```ruby
|
201
|
+
user = User.find_by_email "esther@example.com"
|
202
|
+
user.permisi.role?(:admin) # eager loads roles
|
203
|
+
user.permisi.role?(:admin) # uses the eager-loaded roles
|
204
|
+
user.permisi.has_role?(:admin) # uses the eager-loaded roles
|
205
|
+
```
|
206
|
+
|
207
|
+
### Actor permissions memoization
|
208
|
+
|
209
|
+
To check whether or not an actor is allowed to perform a specific action (`#may_i?`), Permisi will check on the actor's permissions which is constructed in the following steps:
|
210
|
+
|
211
|
+
- load all the roles an actor have from the database
|
212
|
+
- initialize an empty aggregate hash
|
213
|
+
- for each role, merge its permissions hash to the aggregate hash
|
214
|
+
|
215
|
+
Deserializing the hashes from the database and deeply-merging them into an aggregate hash can be expensive, so it will only happen to an instance of actor only once through memoization.
|
216
|
+
|
217
|
+
### Actor permissions caching
|
218
|
+
|
219
|
+
Although memoization helps, the permission hash construction will still occur every time an actor is initialized. To alleviate this, we can introduce a caching layer so that we can skip the hash construction for fresh actors. You must configure a cache store to use caching:
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
# config/initializers/permisi.rb
|
223
|
+
|
224
|
+
Permisi.init do |config|
|
225
|
+
# You can use the default Rails cache store
|
226
|
+
config.cache_store = Rails.cache
|
227
|
+
# or use other cache stores
|
228
|
+
config.cache_store = ActiveSupport::Cache::RedisCacheStore.new(url: ENV['REDIS_URL'])
|
229
|
+
# or
|
230
|
+
config.cache_store = ActiveSupport::Cache::FileStore.new("/home/ukazap/permisi_cache/")
|
231
|
+
end
|
232
|
+
```
|
26
233
|
|
27
|
-
|
234
|
+
You can also roll your own [custom cache store](https://guides.rubyonrails.org/caching_with_rails.html#custom-cache-stores).
|
28
235
|
|
29
|
-
|
236
|
+
### Cache/memo invalidation
|
30
237
|
|
31
|
-
|
238
|
+
The following will trigger actor's permissions cache/memo invalidation:
|
32
239
|
|
33
|
-
|
240
|
+
- adding roles to the actor
|
241
|
+
- removing roles from the actor
|
242
|
+
- editing roles that belongs to the actor
|
34
243
|
|
35
244
|
## Contributing
|
36
245
|
|
37
|
-
|
246
|
+
For development and how to submit improvements, please refer to the [contribution guide](https://github.com/ukazap/permisi/blob/main/CONTRIBUTING.md).
|
38
247
|
|
39
248
|
## License
|
40
249
|
|
@@ -42,4 +251,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
42
251
|
|
43
252
|
## Code of Conduct
|
44
253
|
|
45
|
-
Everyone interacting in the Permisi project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
254
|
+
Everyone interacting in the Permisi project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ukazap/permisi/blob/main/CODE_OF_CONDUCT.md).
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
require "rails/generators/migration"
|
5
|
+
require "rails/generators/active_record"
|
6
|
+
|
7
|
+
module Permisi
|
8
|
+
module Generators
|
9
|
+
class InstallGenerator < Rails::Generators::Base
|
10
|
+
include Rails::Generators::Migration
|
11
|
+
|
12
|
+
source_root File.expand_path("templates", __dir__)
|
13
|
+
|
14
|
+
def self.next_migration_number(path)
|
15
|
+
ActiveRecord::Generators::Base.next_migration_number(path)
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_initializer
|
19
|
+
template "initializer.rb", "config/initializers/permisi.rb"
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_migrations
|
23
|
+
migration_template "migration.rb", "db/migrate/create_permisi_tables.rb", migration_version: migration_version
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def migration_version
|
29
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]" if ActiveRecord.version.version > "5"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "permisi"
|
4
|
+
|
5
|
+
Permisi.init do |config|
|
6
|
+
# Define which backend to use
|
7
|
+
# See https://github.com/ukazap/permisi#configuring-backend
|
8
|
+
config.backend = :active_record
|
9
|
+
|
10
|
+
# Define all permissions available in the system
|
11
|
+
# See https://github.com/ukazap/permisi#configuring-permissions
|
12
|
+
config.permissions = {}
|
13
|
+
|
14
|
+
# Define cache store
|
15
|
+
# See https://github.com/ukazap/permisi#caching
|
16
|
+
config.cache_store = Rails.cache
|
17
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class CreatePermisiTables < ActiveRecord::Migration<%= migration_version %>
|
2
|
+
def up
|
3
|
+
create_table :permisi_actors do |t|
|
4
|
+
t.references :aka, polymorphic: true
|
5
|
+
t.timestamps
|
6
|
+
end
|
7
|
+
|
8
|
+
add_index :permisi_actors, [:aka_type, :aka_id]
|
9
|
+
|
10
|
+
create_table :permisi_roles do |t|
|
11
|
+
t.string :slug, null: false, unique: true
|
12
|
+
t.string :name, null: false, unique: true
|
13
|
+
t.json :permissions
|
14
|
+
t.timestamps
|
15
|
+
end
|
16
|
+
|
17
|
+
create_table :permisi_actor_roles do |t|
|
18
|
+
t.belongs_to :actor
|
19
|
+
t.belongs_to :role
|
20
|
+
end
|
21
|
+
|
22
|
+
add_index :permisi_actor_roles, [:actor_id, :role_id], unique: true
|
23
|
+
end
|
24
|
+
|
25
|
+
def down
|
26
|
+
drop_table :permisi_actor_roles
|
27
|
+
drop_table :permisi_roles
|
28
|
+
drop_table :permisi_actors
|
29
|
+
end
|
30
|
+
end
|
data/lib/permisi.rb
CHANGED
@@ -1,8 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require "active_model/type"
|
4
|
+
require "active_support"
|
5
|
+
require "zeitwerk"
|
4
6
|
|
5
7
|
module Permisi
|
6
|
-
|
7
|
-
|
8
|
+
LOADER = Zeitwerk::Loader.for_gem
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def init
|
12
|
+
yield config if block_given?
|
13
|
+
end
|
14
|
+
|
15
|
+
def config
|
16
|
+
@config ||= Config.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def actors
|
20
|
+
config.backend.actors
|
21
|
+
end
|
22
|
+
|
23
|
+
def actor(aka)
|
24
|
+
config.backend.findsert_actor(aka)
|
25
|
+
end
|
26
|
+
|
27
|
+
def roles
|
28
|
+
config.backend.roles
|
29
|
+
end
|
30
|
+
end
|
8
31
|
end
|
32
|
+
|
33
|
+
Permisi::LOADER.ignore("#{__dir__}/generators")
|
34
|
+
Permisi::LOADER.ignore("#{__dir__}/permisi/backend/mongoid.rb") # todo
|
35
|
+
Permisi::LOADER.setup
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Permisi
|
4
|
+
module Backend
|
5
|
+
class InvalidBackend < StandardError
|
6
|
+
def initialize(message = "Please check https://github.com/ukazap/permisi#configuring-backend")
|
7
|
+
super
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module NullBackend
|
12
|
+
class << self
|
13
|
+
def findsert_actor(_aka)
|
14
|
+
raise InvalidBackend
|
15
|
+
end
|
16
|
+
|
17
|
+
def actors
|
18
|
+
raise InvalidBackend
|
19
|
+
end
|
20
|
+
|
21
|
+
def roles
|
22
|
+
raise InvalidBackend
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record"
|
4
|
+
|
5
|
+
module Permisi
|
6
|
+
module Backend
|
7
|
+
module ActiveRecord
|
8
|
+
class << self
|
9
|
+
def table_name_prefix
|
10
|
+
"permisi_"
|
11
|
+
end
|
12
|
+
|
13
|
+
def findsert_actor(aka)
|
14
|
+
Actor.find_or_create_by(aka: aka)
|
15
|
+
end
|
16
|
+
|
17
|
+
def actors
|
18
|
+
Actor.all
|
19
|
+
end
|
20
|
+
|
21
|
+
def roles
|
22
|
+
Role.all
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Permisi
|
4
|
+
module Backend
|
5
|
+
module ActiveRecord
|
6
|
+
class Actor < ::ActiveRecord::Base
|
7
|
+
belongs_to :aka, polymorphic: true, touch: true
|
8
|
+
has_many :actor_roles, dependent: :destroy
|
9
|
+
has_many :roles, -> { distinct }, through: :actor_roles
|
10
|
+
|
11
|
+
after_commit :reset_permissions
|
12
|
+
|
13
|
+
def roles
|
14
|
+
super.extend(ActorRolesCollectionProxy)
|
15
|
+
end
|
16
|
+
|
17
|
+
def role?(role_slug)
|
18
|
+
roles.load.any? { |role| role.slug == role_slug.to_s }
|
19
|
+
end
|
20
|
+
|
21
|
+
def may_i?(action_path)
|
22
|
+
PermissionUtil.allows?(permissions, action_path)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Memoized and cached actor permissions
|
26
|
+
def permissions
|
27
|
+
@permissions ||= Permisi.config.cache_store.fetch("#{cache_key}-p") { aggregate_permissions }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Aggregate permissions from all roles an actor plays
|
31
|
+
def aggregate_permissions
|
32
|
+
roles.load.inject(HashWithIndifferentAccess.new) do |aggregate, role|
|
33
|
+
aggregate.deep_merge(role.permissions) do |_key, effect, another_effect|
|
34
|
+
effect == true || another_effect == true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def reset_permissions
|
40
|
+
@permissions = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
alias may? may_i?
|
44
|
+
alias has_role? role?
|
45
|
+
|
46
|
+
module ActorRolesCollectionProxy
|
47
|
+
def <<(new_role)
|
48
|
+
super
|
49
|
+
rescue ::ActiveRecord::RecordNotUnique
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete(*records)
|
54
|
+
warn "WARNING: `#delete(*records)` won't invalidate the cache, use `#destroy(*records)` instead."
|
55
|
+
super
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Permisi
|
4
|
+
module Backend
|
5
|
+
module ActiveRecord
|
6
|
+
class ActorRole < ::ActiveRecord::Base
|
7
|
+
belongs_to :actor, touch: true
|
8
|
+
belongs_to :role
|
9
|
+
|
10
|
+
after_destroy :touch_actor
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def touch_actor
|
15
|
+
actor.touch
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Permisi
|
4
|
+
module Backend
|
5
|
+
module ActiveRecord
|
6
|
+
class Role < ::ActiveRecord::Base
|
7
|
+
has_many :actor_roles, dependent: :destroy
|
8
|
+
has_many :actors, through: :actor_roles
|
9
|
+
has_many :akas, through: :actors
|
10
|
+
|
11
|
+
validates_presence_of :name, :slug
|
12
|
+
validates_uniqueness_of :name, :slug
|
13
|
+
|
14
|
+
after_initialize :set_default_permissions
|
15
|
+
before_validation :sanitize_attributes
|
16
|
+
after_update :touch_actor_roles
|
17
|
+
|
18
|
+
serialize :permissions, Permisi::PermissionUtil::Serializer
|
19
|
+
|
20
|
+
def allows?(action_path)
|
21
|
+
Permisi::PermissionUtil.allows?(permissions, action_path)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def set_default_permissions
|
27
|
+
self.permissions ||= HashWithIndifferentAccess.new if new_record?
|
28
|
+
end
|
29
|
+
|
30
|
+
def sanitize_attributes
|
31
|
+
self.name ||= slug.try(:titleize)
|
32
|
+
self.permissions = Permisi::PermissionUtil.sanitize_permissions(self.permissions)
|
33
|
+
end
|
34
|
+
|
35
|
+
def touch_actor_roles
|
36
|
+
actor_roles.each(&:touch)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Permisi
|
4
|
+
class Config
|
5
|
+
class InvalidCacheStore < StandardError; end
|
6
|
+
|
7
|
+
NULL_CACHE_STORE = ActiveSupport::Cache::NullStore.new
|
8
|
+
|
9
|
+
attr_reader :permissions, :default_permissions
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@permissions = ::HashWithIndifferentAccess.new
|
13
|
+
@default_permissions = ::HashWithIndifferentAccess.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def backend=(chosen_backend)
|
17
|
+
chosen_backend = "::Permisi::Backend::#{chosen_backend.to_s.classify}".constantize if chosen_backend.is_a? Symbol
|
18
|
+
|
19
|
+
if chosen_backend == Backend::ActiveRecord && VERSION == "0.1.4"
|
20
|
+
warn <<~MESSAGE
|
21
|
+
|
22
|
+
WARNING: If you are upgrading from Permisi >v0.1.4, please create the following migration:
|
23
|
+
`add_index :permisi_actor_roles, [:actor_id, :role_id], unique: true`
|
24
|
+
|
25
|
+
MESSAGE
|
26
|
+
end
|
27
|
+
|
28
|
+
@backend = chosen_backend
|
29
|
+
rescue NameError
|
30
|
+
raise Backend::InvalidBackend
|
31
|
+
end
|
32
|
+
|
33
|
+
def backend
|
34
|
+
@backend || Backend::NullBackend
|
35
|
+
end
|
36
|
+
|
37
|
+
def permissions=(permissions_hash)
|
38
|
+
permissions_hash = HashWithIndifferentAccess.new(permissions_hash)
|
39
|
+
@default_permissions = PermissionUtil.transform_namespace(permissions_hash)
|
40
|
+
@permissions = permissions_hash
|
41
|
+
end
|
42
|
+
|
43
|
+
def cache_store=(cache_store)
|
44
|
+
raise InvalidCacheStore unless cache_store.respond_to?(:fetch)
|
45
|
+
|
46
|
+
@cache_store = cache_store
|
47
|
+
end
|
48
|
+
|
49
|
+
def cache_store
|
50
|
+
@cache_store || NULL_CACHE_STORE
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Permisi
|
4
|
+
module PermissionUtil
|
5
|
+
class InvalidNamespace < StandardError; end
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def allows?(hash, action_path)
|
9
|
+
return false unless hash.is_a?(Hash)
|
10
|
+
|
11
|
+
action_path_arr = action_path.split(".")
|
12
|
+
begin
|
13
|
+
!Permisi.config.default_permissions.dig(*action_path_arr).nil?
|
14
|
+
rescue StandardError
|
15
|
+
false
|
16
|
+
end &&
|
17
|
+
hash.dig(*action_path_arr) == true
|
18
|
+
end
|
19
|
+
|
20
|
+
def transform_namespace(namespace, current_path: nil)
|
21
|
+
HashWithIndifferentAccess.new.tap do |transformed|
|
22
|
+
namespace.each_pair do |key, value|
|
23
|
+
if !value.is_a? Array
|
24
|
+
raise InvalidNamespace,
|
25
|
+
"`#{[current_path, key].compact.join(".")}` should be an array"
|
26
|
+
end
|
27
|
+
|
28
|
+
if key.to_s.include?(".")
|
29
|
+
raise InvalidNamespace, "namespace or action should not contain period: `#{key}`"
|
30
|
+
end
|
31
|
+
|
32
|
+
value.each.with_index do |arr_v, arr_i|
|
33
|
+
case arr_v
|
34
|
+
when Symbol
|
35
|
+
if arr_v.to_s.include?(".")
|
36
|
+
raise InvalidNamespace, "namespace or action should not contain period: `#{arr_v}`"
|
37
|
+
end
|
38
|
+
|
39
|
+
transformed[key] ||= ::HashWithIndifferentAccess.new
|
40
|
+
if transformed[key].key? arr_v
|
41
|
+
raise InvalidNamespace, "duplicate entry: `#{[current_path, key, arr_v].compact.join(".")}`"
|
42
|
+
end
|
43
|
+
|
44
|
+
transformed[key][arr_v] = false
|
45
|
+
when Hash
|
46
|
+
transform_namespace(arr_v,
|
47
|
+
current_path: [current_path, key].compact.join(".")).each_pair do |ts_k, ts_v|
|
48
|
+
transformed[key] ||= ::HashWithIndifferentAccess.new
|
49
|
+
if transformed[key].key? ts_k
|
50
|
+
raise InvalidNamespace, "duplicate entry: `#{[current_path, key, ts_k].compact.join(".")}`"
|
51
|
+
end
|
52
|
+
|
53
|
+
transformed[key][ts_k] = ts_v
|
54
|
+
end
|
55
|
+
else
|
56
|
+
raise InvalidNamespace,
|
57
|
+
"`#{[current_path, key].compact.join(".")}[#{arr_i}]` should be a symbol or a hash"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def sanitize_permissions(permission_hash)
|
65
|
+
__deeply_sanitize_permissions(permission_hash, template: Permisi.config.default_permissions)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def __deeply_sanitize_permissions(permission_hash, template: {})
|
71
|
+
HashWithIndifferentAccess.new.tap do |sanitized|
|
72
|
+
permission_hash.each_pair do |key, value|
|
73
|
+
next unless template.key?(key)
|
74
|
+
|
75
|
+
sanitized[key] = if value.is_a?(Hash)
|
76
|
+
__deeply_sanitize_permissions(value, template: template[key])
|
77
|
+
else
|
78
|
+
__cast_value_to_boolean(value)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def __cast_value_to_boolean(value)
|
85
|
+
bool = ActiveModel::Type::Boolean.new.cast(value)
|
86
|
+
bool ||= false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class Serializer
|
91
|
+
def self.dump(hash)
|
92
|
+
hash
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.load(hash)
|
96
|
+
(hash.is_a?(Hash) ? hash : {}).with_indifferent_access
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/permisi/version.rb
CHANGED
data/permisi.gemspec
CHANGED
@@ -8,15 +8,21 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.authors = ["Ukaza Perdana"]
|
9
9
|
spec.email = ["ukaza@hey.com"]
|
10
10
|
|
11
|
-
spec.summary = "Simple and dynamic
|
12
|
-
|
11
|
+
spec.summary = "Simple and dynamic role-based access control for Rails"
|
12
|
+
|
13
|
+
spec.description = <<~DESCRIPTION
|
14
|
+
Permisi provides a way of dynamically declaring user rights (a.k.a. permissions) using a simple role-based access control scheme.
|
15
|
+
A user may be associated to multiple roles with a different set of permissions in each role.
|
16
|
+
The roles and user-roles association can be dynamically defined and changed on runtime.
|
17
|
+
DESCRIPTION
|
18
|
+
|
13
19
|
spec.homepage = "https://github.com/ukazap/permisi"
|
14
20
|
spec.license = "MIT"
|
15
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 2.4.
|
21
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.4.4")
|
16
22
|
|
17
23
|
spec.metadata["homepage_uri"] = spec.homepage
|
18
24
|
spec.metadata["source_code_uri"] = spec.homepage
|
19
|
-
spec.metadata["changelog_uri"] = "
|
25
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
20
26
|
|
21
27
|
# Specify which files should be added to the gem when it is released.
|
22
28
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -27,8 +33,10 @@ Gem::Specification.new do |spec|
|
|
27
33
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
28
34
|
spec.require_paths = ["lib"]
|
29
35
|
|
30
|
-
|
31
|
-
|
36
|
+
spec.add_dependency "activemodel", ">= 3.2.0"
|
37
|
+
spec.add_dependency "activerecord", ">= 3.2.0"
|
38
|
+
spec.add_dependency "activesupport", ">= 3.2.0"
|
39
|
+
spec.add_dependency "zeitwerk", ["~> 2.4", ">= 2.4.2"]
|
32
40
|
|
33
41
|
# For more information and examples about making a new gem, checkout our
|
34
42
|
# guide at: https://bundler.io/guides/creating_gem.html
|
metadata
CHANGED
@@ -1,30 +1,94 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: permisi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ukaza Perdana
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-02-
|
12
|
-
dependencies:
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
date: 2021-02-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activemodel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.2.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.2.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.2.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.2.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.2.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.2.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: zeitwerk
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.4'
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: 2.4.2
|
65
|
+
type: :runtime
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - "~>"
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '2.4'
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 2.4.2
|
75
|
+
description: |
|
76
|
+
Permisi provides a way of dynamically declaring user rights (a.k.a. permissions) using a simple role-based access control scheme.
|
77
|
+
A user may be associated to multiple roles with a different set of permissions in each role.
|
78
|
+
The roles and user-roles association can be dynamically defined and changed on runtime.
|
16
79
|
email:
|
17
80
|
- ukaza@hey.com
|
18
81
|
executables: []
|
19
82
|
extensions: []
|
20
83
|
extra_rdoc_files: []
|
21
84
|
files:
|
22
|
-
- ".github/workflows/
|
85
|
+
- ".github/workflows/ci.yml"
|
23
86
|
- ".gitignore"
|
24
87
|
- ".rspec"
|
25
88
|
- ".rubocop.yml"
|
26
89
|
- CHANGELOG.md
|
27
90
|
- CODE_OF_CONDUCT.md
|
91
|
+
- CONTRIBUTING.md
|
28
92
|
- Gemfile
|
29
93
|
- Gemfile.lock
|
30
94
|
- LICENSE.txt
|
@@ -32,7 +96,19 @@ files:
|
|
32
96
|
- Rakefile
|
33
97
|
- bin/console
|
34
98
|
- bin/setup
|
99
|
+
- lib/generators/permisi/install_generator.rb
|
100
|
+
- lib/generators/permisi/templates/initializer.rb
|
101
|
+
- lib/generators/permisi/templates/migration.rb
|
35
102
|
- lib/permisi.rb
|
103
|
+
- lib/permisi/actable.rb
|
104
|
+
- lib/permisi/backend.rb
|
105
|
+
- lib/permisi/backend/active_record.rb
|
106
|
+
- lib/permisi/backend/active_record/actor.rb
|
107
|
+
- lib/permisi/backend/active_record/actor_role.rb
|
108
|
+
- lib/permisi/backend/active_record/role.rb
|
109
|
+
- lib/permisi/backend/mongoid.rb
|
110
|
+
- lib/permisi/config.rb
|
111
|
+
- lib/permisi/permission_util.rb
|
36
112
|
- lib/permisi/version.rb
|
37
113
|
- permisi.gemspec
|
38
114
|
homepage: https://github.com/ukazap/permisi
|
@@ -41,7 +117,7 @@ licenses:
|
|
41
117
|
metadata:
|
42
118
|
homepage_uri: https://github.com/ukazap/permisi
|
43
119
|
source_code_uri: https://github.com/ukazap/permisi
|
44
|
-
changelog_uri: https://github.com/ukazap/permisi/blob
|
120
|
+
changelog_uri: https://github.com/ukazap/permisi/blob/main/CHANGELOG.md
|
45
121
|
post_install_message:
|
46
122
|
rdoc_options: []
|
47
123
|
require_paths:
|
@@ -50,7 +126,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
50
126
|
requirements:
|
51
127
|
- - ">="
|
52
128
|
- !ruby/object:Gem::Version
|
53
|
-
version: 2.4.
|
129
|
+
version: 2.4.4
|
54
130
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
131
|
requirements:
|
56
132
|
- - ">="
|
@@ -60,5 +136,5 @@ requirements: []
|
|
60
136
|
rubygems_version: 3.2.3
|
61
137
|
signing_key:
|
62
138
|
specification_version: 4
|
63
|
-
summary: Simple and dynamic
|
139
|
+
summary: Simple and dynamic role-based access control for Rails
|
64
140
|
test_files: []
|
data/.github/workflows/main.yml
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
name: Ruby
|
2
|
-
|
3
|
-
on: [push,pull_request]
|
4
|
-
|
5
|
-
jobs:
|
6
|
-
build:
|
7
|
-
runs-on: ubuntu-latest
|
8
|
-
steps:
|
9
|
-
- uses: actions/checkout@v2
|
10
|
-
- name: Set up Ruby
|
11
|
-
uses: ruby/setup-ruby@v1
|
12
|
-
with:
|
13
|
-
ruby-version: 3.0.0
|
14
|
-
- name: Run the default task
|
15
|
-
run: |
|
16
|
-
gem install bundler -v 2.2.5
|
17
|
-
bundle install
|
18
|
-
bundle exec rake
|