papers_please 0.0.1.beta → 0.0.2.beta
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 +5 -5
- data/.byebug_history +60 -0
- data/Gemfile.lock +45 -0
- data/README.md +105 -1
- data/lib/papers_please/errors.rb +11 -0
- data/lib/papers_please/permission.rb +36 -0
- data/lib/papers_please/policy.rb +68 -0
- data/lib/papers_please/role.rb +82 -0
- data/lib/papers_please/version.rb +1 -1
- data/lib/papers_please.rb +5 -1
- data/papers_please.gemspec +2 -0
- metadata +37 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d12ab3dd8981fb3a6fb74a02b0f34e917438792c2151562c9945a04c79d50d9d
|
4
|
+
data.tar.gz: 694b9b96b9057e122392a2382488bfbecc877b881cf681e07dcb066091d2bd8c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 80a53cd2301779d6a332c1c01d495f54035886d64a5d3d23e3434de008b5fac367e9a351eb96ef1a842b9c1ae4207045c28c739a43d0f3e78def7e7836f65680
|
7
|
+
data.tar.gz: cc6193a203effc3bff3f3351359550ac71fcf4ca83bd1d1f0cd31ed918f2a083c410638e9c6c2e2e1fd2ea21da3cb7555c57a34f3118bf8c5472a5d35e72abdb
|
data/.byebug_history
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
q
|
2
|
+
@policy
|
3
|
+
q
|
4
|
+
permission
|
5
|
+
c
|
6
|
+
permission
|
7
|
+
c
|
8
|
+
permission
|
9
|
+
c
|
10
|
+
permission
|
11
|
+
q
|
12
|
+
res.include?(obj)
|
13
|
+
posts.include?(obj)
|
14
|
+
posts.include(obj)
|
15
|
+
res
|
16
|
+
obj
|
17
|
+
c
|
18
|
+
obj
|
19
|
+
res.include?(obj)
|
20
|
+
res.include?
|
21
|
+
res
|
22
|
+
c
|
23
|
+
u.posts
|
24
|
+
u
|
25
|
+
c
|
26
|
+
u
|
27
|
+
c
|
28
|
+
role
|
29
|
+
q
|
30
|
+
role
|
31
|
+
q
|
32
|
+
applicable_roles.count
|
33
|
+
applicable_roles
|
34
|
+
c
|
35
|
+
q
|
36
|
+
@user
|
37
|
+
role.applies_to?(@user)
|
38
|
+
role
|
39
|
+
c
|
40
|
+
q
|
41
|
+
applicable_roles
|
42
|
+
c
|
43
|
+
q
|
44
|
+
klass
|
45
|
+
action
|
46
|
+
permissions
|
47
|
+
permission_exists?(action, klass)
|
48
|
+
action
|
49
|
+
q
|
50
|
+
n
|
51
|
+
s
|
52
|
+
actions
|
53
|
+
c
|
54
|
+
n
|
55
|
+
role
|
56
|
+
c
|
57
|
+
n
|
58
|
+
roles.key?(:admin)
|
59
|
+
roles
|
60
|
+
name
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
papers_please (0.0.1.beta)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
byebug (10.0.2)
|
10
|
+
diff-lcs (1.3)
|
11
|
+
docile (1.3.1)
|
12
|
+
json (2.1.0)
|
13
|
+
rake (10.5.0)
|
14
|
+
rspec (3.7.0)
|
15
|
+
rspec-core (~> 3.7.0)
|
16
|
+
rspec-expectations (~> 3.7.0)
|
17
|
+
rspec-mocks (~> 3.7.0)
|
18
|
+
rspec-core (3.7.1)
|
19
|
+
rspec-support (~> 3.7.0)
|
20
|
+
rspec-expectations (3.7.0)
|
21
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
22
|
+
rspec-support (~> 3.7.0)
|
23
|
+
rspec-mocks (3.7.0)
|
24
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
25
|
+
rspec-support (~> 3.7.0)
|
26
|
+
rspec-support (3.7.1)
|
27
|
+
simplecov (0.16.1)
|
28
|
+
docile (~> 1.1)
|
29
|
+
json (>= 1.8, < 3)
|
30
|
+
simplecov-html (~> 0.10.0)
|
31
|
+
simplecov-html (0.10.2)
|
32
|
+
|
33
|
+
PLATFORMS
|
34
|
+
ruby
|
35
|
+
|
36
|
+
DEPENDENCIES
|
37
|
+
bundler (~> 1.16)
|
38
|
+
byebug
|
39
|
+
papers_please!
|
40
|
+
rake (~> 10.0)
|
41
|
+
rspec (~> 3.0)
|
42
|
+
simplecov
|
43
|
+
|
44
|
+
BUNDLED WITH
|
45
|
+
1.16.1
|
data/README.md
CHANGED
@@ -2,7 +2,98 @@
|
|
2
2
|
|
3
3
|
A roles and permissions gem from Apsis Labs.
|
4
4
|
|
5
|
-
**NOTE**: Still under heavy development, definitely not suitable for anything remotely resembling production usage.
|
5
|
+
**NOTE**: Still under heavy development, definitely not suitable for anything remotely resembling production usage. Very unlikely to even work.
|
6
|
+
|
7
|
+
## Example
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# app/policies/access_policy.rb
|
11
|
+
class AccessPolicy < PapersPlease::Policy
|
12
|
+
config do
|
13
|
+
# Define a role in a block
|
14
|
+
role :admin, (proc { |u| u.admin? }) do
|
15
|
+
grant [:manage, :archive], Post
|
16
|
+
end
|
17
|
+
|
18
|
+
# Define a role in a class
|
19
|
+
role :member, MemberRole
|
20
|
+
|
21
|
+
# Define a role with no predicate
|
22
|
+
role :guest do
|
23
|
+
grant [:read], Post, predicate: (proc { |u, post| !post.archived? })
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# app/policies/roles/member_role.rb
|
29
|
+
class MemberRole < PapersPlease::Role
|
30
|
+
predicate { |user| user.member? }
|
31
|
+
|
32
|
+
config do
|
33
|
+
grant :create, Post
|
34
|
+
grant [:read, :update], Post, query: (proc { |u| u.posts })
|
35
|
+
grant :archive, Post, query: method(:published_posts)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def published_posts(user, klass)
|
41
|
+
user.posts.where(status: :published)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# app/controllers/posts_controller.rb
|
46
|
+
class PostsController < ApplicationController
|
47
|
+
# GET /posts
|
48
|
+
def index
|
49
|
+
@posts = policy.query(:read, Post)
|
50
|
+
render json: @posts
|
51
|
+
end
|
52
|
+
|
53
|
+
# GET /posts/:id
|
54
|
+
def show
|
55
|
+
@post = Post.find(params[:id])
|
56
|
+
policy.authorize! :read, @post
|
57
|
+
|
58
|
+
render json: @post
|
59
|
+
end
|
60
|
+
|
61
|
+
# POST /posts/:id/archive
|
62
|
+
def archive
|
63
|
+
@post = Post.find(params[:id])
|
64
|
+
policy.authorize! :archive, @post
|
65
|
+
|
66
|
+
@post.update!(archived: true)
|
67
|
+
render json: @post
|
68
|
+
end
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
## A helpful CLI
|
73
|
+
|
74
|
+
```bash
|
75
|
+
$ rails papers_please:roles
|
76
|
+
|
77
|
+
# =>
|
78
|
+
# | role | permission | object |
|
79
|
+
# | :------ | :--------- | :----- |
|
80
|
+
# | :admin | :create | Post |
|
81
|
+
# | | :read | Post |
|
82
|
+
# | | :update | Post |
|
83
|
+
# | | :destroy | Post |
|
84
|
+
# | | :archive | Post |
|
85
|
+
# | | | |
|
86
|
+
# | :member | :create | Post |
|
87
|
+
# | | :read | Post |
|
88
|
+
# | | :update | Post |
|
89
|
+
# | | :archive | Post |
|
90
|
+
# | | | |
|
91
|
+
# | :guest | :read | Post |
|
92
|
+
|
93
|
+
$ rails papers_please:annotate [app/policies/access_policy.rb]
|
94
|
+
|
95
|
+
# => output roles table to top of AccessPolicy file
|
96
|
+
```
|
6
97
|
|
7
98
|
## Installation
|
8
99
|
|
@@ -24,6 +115,15 @@ Or install it yourself as:
|
|
24
115
|
|
25
116
|
TODO: Write usage instructions here
|
26
117
|
|
118
|
+
## Theory
|
119
|
+
|
120
|
+
The structure of `papers_please` is very simple. At its core, it is a mechanism for storing and retrieving `Procs`. In an authorization context, these `Procs` answer two questions:
|
121
|
+
|
122
|
+
1. Given a specific user and a specific permission, which objects am I allowed to operate on?
|
123
|
+
2. Given a specific user and a specific object, do I have a specific permission?
|
124
|
+
|
125
|
+
The machinery of `papers_please` tries to simplify the organization and subsequent access to these questions as much as possible.
|
126
|
+
|
27
127
|
## Development
|
28
128
|
|
29
129
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -34,6 +134,10 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
34
134
|
|
35
135
|
Bug reports and pull requests are welcome on GitHub at https://github.com/wkirby/papers_please.
|
36
136
|
|
137
|
+
## Special Thanks
|
138
|
+
|
139
|
+
This owes its existence to [`AccessGranted`](https://github.com/chaps-io/access-granted). Thanks!
|
140
|
+
|
37
141
|
## License
|
38
142
|
|
39
143
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module PapersPlease
|
2
|
+
class Permission
|
3
|
+
attr_accessor :key, :subject, :query, :predicate
|
4
|
+
|
5
|
+
def initialize(key, subject, query: nil, predicate: nil)
|
6
|
+
@key = key
|
7
|
+
@subject = subject
|
8
|
+
@query = query
|
9
|
+
@predicate = predicate
|
10
|
+
end
|
11
|
+
|
12
|
+
def matches?(key, subject)
|
13
|
+
key_matches?(key) && subject_matches?(subject)
|
14
|
+
end
|
15
|
+
|
16
|
+
def granted?(*args)
|
17
|
+
return predicate.call(*args) if predicate.is_a? Proc
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch(*args)
|
22
|
+
return query.call(*args) if query.is_a? Proc
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def key_matches?(key)
|
29
|
+
key == @key
|
30
|
+
end
|
31
|
+
|
32
|
+
def subject_matches?(subject)
|
33
|
+
subject == @subject || subject.class <= @subject
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module PapersPlease
|
2
|
+
class Policy
|
3
|
+
attr_accessor :roles
|
4
|
+
attr_reader :user
|
5
|
+
|
6
|
+
def initialize(user)
|
7
|
+
@user = user
|
8
|
+
@roles = {}
|
9
|
+
@cache = {}
|
10
|
+
|
11
|
+
configure
|
12
|
+
end
|
13
|
+
|
14
|
+
def configure
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
# Add a role to the Policy
|
19
|
+
def add_role(name, predicate = nil, &block)
|
20
|
+
name = name.to_sym
|
21
|
+
raise DuplicateRole if roles.key?(name)
|
22
|
+
|
23
|
+
role = Role.new(name, predicate: predicate, definition: block)
|
24
|
+
roles[name] = role
|
25
|
+
|
26
|
+
role
|
27
|
+
end
|
28
|
+
alias role add_role
|
29
|
+
|
30
|
+
# Look up a stored permission block and call with
|
31
|
+
# the current user and subject
|
32
|
+
def can?(action, subject = nil)
|
33
|
+
applicable_roles.each do |_, role|
|
34
|
+
permission = role.find_permission(action, subject)
|
35
|
+
return permission.granted?(user, subject, action) unless permission.nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
def cannot?(*args)
|
42
|
+
!can?(*args)
|
43
|
+
end
|
44
|
+
|
45
|
+
def authorize!(action, subject)
|
46
|
+
raise AccessDenied.new(action, subject) if cannot?(action, subject)
|
47
|
+
subject
|
48
|
+
end
|
49
|
+
|
50
|
+
# Look up a stored scope block and call with the
|
51
|
+
# current user and class
|
52
|
+
def scope_for(action, klass)
|
53
|
+
applicable_roles.each do |_, role|
|
54
|
+
permission = role.find_permission(action, klass)
|
55
|
+
return permission.fetch(user, klass, action) unless permission.nil?
|
56
|
+
end
|
57
|
+
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
|
61
|
+
# Fetch roles that apply to the current user
|
62
|
+
def applicable_roles
|
63
|
+
@applicable_roles ||= roles.select do |_, role|
|
64
|
+
role.applies_to?(user)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module PapersPlease
|
2
|
+
class Role
|
3
|
+
attr_reader :name, :predicate, :permissions
|
4
|
+
|
5
|
+
def initialize(name, predicate: nil, definition: nil)
|
6
|
+
@name = name
|
7
|
+
@predicate = predicate
|
8
|
+
@permissions = []
|
9
|
+
|
10
|
+
instance_eval(&definition) unless definition.nil?
|
11
|
+
end
|
12
|
+
|
13
|
+
def applies_to?(user)
|
14
|
+
return @predicate.call(user) if @predicate.is_a? Proc
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_permission(actions, klass, query: nil, predicate: nil)
|
19
|
+
prepare_actions(actions).each do |action|
|
20
|
+
raise DuplicatePermission if permission_exists?(action, klass)
|
21
|
+
|
22
|
+
has_query = query.is_a?(Proc)
|
23
|
+
has_predicate = predicate.is_a?(Proc)
|
24
|
+
permission = Permission.new(action, klass)
|
25
|
+
|
26
|
+
if has_query && has_predicate
|
27
|
+
# Both query & predicate provided
|
28
|
+
|
29
|
+
permission.query = query
|
30
|
+
permission.predicate = predicate
|
31
|
+
elsif has_query && !has_predicate
|
32
|
+
# Only query provided
|
33
|
+
|
34
|
+
permission.query = query
|
35
|
+
|
36
|
+
if action == :create && actions == :manage
|
37
|
+
# If the action is :create, expanded from :manage
|
38
|
+
# then we set the default all predicate
|
39
|
+
permission.predicate = (proc { true })
|
40
|
+
else
|
41
|
+
# Otherwise the default predicate is to check
|
42
|
+
# for inclusion in the returned relationship
|
43
|
+
permission.predicate = (proc { |user, obj|
|
44
|
+
res = query.call(user, klass, action)
|
45
|
+
res.respond_to?(:include?) && res.include?(obj)
|
46
|
+
})
|
47
|
+
end
|
48
|
+
elsif !has_query && has_predicate
|
49
|
+
# Only predicate provided
|
50
|
+
|
51
|
+
permission.predicate = predicate
|
52
|
+
else
|
53
|
+
# Neither provided
|
54
|
+
permission.query = (proc { klass.all })
|
55
|
+
permission.predicate = (proc { true })
|
56
|
+
end
|
57
|
+
|
58
|
+
permissions << permission
|
59
|
+
end
|
60
|
+
end
|
61
|
+
alias grant add_permission
|
62
|
+
|
63
|
+
def find_permission(action, subject)
|
64
|
+
permissions.detect do |permission|
|
65
|
+
permission.matches? action, subject
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def permission_exists?(action, subject)
|
70
|
+
!find_permission(action, subject).nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
# Wrap actions, translating :manage into :crud
|
76
|
+
def prepare_actions(action)
|
77
|
+
Array(*[action]).flat_map do |a|
|
78
|
+
a == :manage ? [:create, :read, :update, :destroy] : [a]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/papers_please.rb
CHANGED
data/papers_please.gemspec
CHANGED
@@ -24,4 +24,6 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_development_dependency "bundler", "~> 1.16"
|
25
25
|
spec.add_development_dependency "rake", "~> 10.0"
|
26
26
|
spec.add_development_dependency "rspec", "~> 3.0"
|
27
|
+
spec.add_development_dependency "byebug"
|
28
|
+
spec.add_development_dependency 'simplecov'
|
27
29
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: papers_please
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2.beta
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Apsis Labs
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-10-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,6 +52,34 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: byebug
|
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: simplecov
|
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'
|
55
83
|
description:
|
56
84
|
email:
|
57
85
|
- wyatt@apsis.io
|
@@ -59,16 +87,22 @@ executables: []
|
|
59
87
|
extensions: []
|
60
88
|
extra_rdoc_files: []
|
61
89
|
files:
|
90
|
+
- ".byebug_history"
|
62
91
|
- ".gitignore"
|
63
92
|
- ".rspec"
|
64
93
|
- ".travis.yml"
|
65
94
|
- Gemfile
|
95
|
+
- Gemfile.lock
|
66
96
|
- LICENSE.txt
|
67
97
|
- README.md
|
68
98
|
- Rakefile
|
69
99
|
- bin/console
|
70
100
|
- bin/setup
|
71
101
|
- lib/papers_please.rb
|
102
|
+
- lib/papers_please/errors.rb
|
103
|
+
- lib/papers_please/permission.rb
|
104
|
+
- lib/papers_please/policy.rb
|
105
|
+
- lib/papers_please/role.rb
|
72
106
|
- lib/papers_please/version.rb
|
73
107
|
- papers_please.gemspec
|
74
108
|
homepage: http://apsis.io
|
@@ -91,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
125
|
version: 1.3.1
|
92
126
|
requirements: []
|
93
127
|
rubyforge_project:
|
94
|
-
rubygems_version: 2.6
|
128
|
+
rubygems_version: 2.7.6
|
95
129
|
signing_key:
|
96
130
|
specification_version: 4
|
97
131
|
summary: A roles & permissions gem for ruby applications.
|