u-authorization 1.4.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2d0d221c7afbf5307e6de989945f359e72432ecdff115f4da85a6d51df147f5
4
- data.tar.gz: 595029aef79e60b483373a3b7ad4eef586bc90944bc8533a1640575bccbe42c7
3
+ metadata.gz: 49ee3c29a92d117c33706e12939a2ced02597514586ae7cf06fe73622d15897f
4
+ data.tar.gz: b1991fa714603aa32a6288447796460287adcd6220a8485d6e82d6040c4e48cf
5
5
  SHA512:
6
- metadata.gz: b8c8d6e819e296f085c045af9c476941c9d3b2faf9269eb7ee7394321908257a1586b267c75dccb6a4b8d75c112ec35b31c3af26722c48dec2a6e99f77e74f05
7
- data.tar.gz: ef58ac9c0ff6b4aa1ed2ccf75fb67af13b9891e886f33a65a44bd64715ccbd49586adf2f9e12553fb17ec70374af5433aef69dccbcea1788a405a9c8ff41b080
6
+ metadata.gz: d480100a329ab41e4707cb124f46d85af1750fc35ee08e615b1cc488714cb8e817394b8b91acf6c965710faf7235a90c13827cf7369c920e974e39d77ad6446b
7
+ data.tar.gz: d427583d3e8c633138e018d2f2d5c339a615976622a6b224d1e0e370634cc09f08bc5ceb158caff480b9fabaa588aed85340282c58a44ca8bf509d857e181860
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'simplecov', require: false, group: :test
4
+
5
+ gem 'minitest', '~> 5.11', '>= 5.11.3'
6
+
7
+ # Specify your gem's dependencies in u-authorization.gemspec
8
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,29 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ u-authorization (1.4.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ docile (1.3.2)
10
+ json (2.2.0)
11
+ minitest (5.11.3)
12
+ rake (10.5.0)
13
+ simplecov (0.17.0)
14
+ docile (~> 1.1)
15
+ json (>= 1.8, < 3)
16
+ simplecov-html (~> 0.10.0)
17
+ simplecov-html (0.10.2)
18
+
19
+ PLATFORMS
20
+ ruby
21
+
22
+ DEPENDENCIES
23
+ minitest (~> 5.11, >= 5.11.3)
24
+ rake (~> 10.0)
25
+ simplecov
26
+ u-authorization!
27
+
28
+ BUNDLED WITH
29
+ 1.17.2
data/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # µ-authorization
2
+
3
+ Simple authorization library and role managment for Ruby.
4
+
5
+ ## Prerequisites
6
+
7
+ > Ruby >= 2.2.0
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+ ```
13
+ gem 'u-authorization'
14
+ ```
15
+
16
+ And then execute:
17
+ ```
18
+ $ bundle
19
+ ```
20
+
21
+ Or install it yourself as:
22
+ ```
23
+ $ gem install u-authorization
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```ruby
29
+ require 'ostruct'
30
+ require 'authorization'
31
+
32
+ role = OpenStruct.new(
33
+ name: 'user',
34
+ permissions: {
35
+ 'visit' => { 'except' => ['billings'] },
36
+ 'edit_users' => false, # Same as: 'edit_users' => { 'any' => false },
37
+ 'export_as_csv' => { 'except' => ['sales'] }
38
+ }
39
+ )
40
+
41
+ user = OpenStruct.new(id: 1, role: role)
42
+
43
+ class SalesPolicy < Micro::Authorization::Policy
44
+ def edit?(record)
45
+ user.id == record.user_id
46
+ end
47
+ end
48
+
49
+ authorization = Micro::Authorization::Model.build(
50
+ permissions: user.role.permissions,
51
+ policies: { default: :sales, sales: SalesPolicy }
52
+ context: {
53
+ user: user,
54
+ permissions: ['dashboard', 'controllers', 'sales', 'index']
55
+ }
56
+ )
57
+
58
+ # Verifying the permissions for the given context
59
+ authorization.permissions.to?('visit') #=> true
60
+ authorization.permissions.to?('export_as_csv') #=> false
61
+
62
+ # Verifying permission for a given feature in different contexts
63
+ has_permission_to = authorization.permissions.to('export_as_csv')
64
+ has_permission_to.context?('billings') #=> true
65
+ has_permission_to.context?('sales') #=> false
66
+
67
+ charge = OpenStruct.new(id: 2, user_id: user.id)
68
+
69
+ # The #to() method fetch and build a policy.
70
+ authorization.to(:sales).edit?(charge) #=> true
71
+
72
+ # :default is the only permitted key to receive
73
+ # another symbol as value (a policy reference).
74
+ authorization.to(:default).edit?(charge) #=> true
75
+
76
+ # #policy() method has a similar behavior of #to(),
77
+ # but if there is a policy named as ":default", it will be fetched and instantiated by default.
78
+ authorization.policy.edit?(charge) #=> true
79
+ authorization.policy(:sales).edit?(charge) #=> true
80
+
81
+
82
+ # Cloning the authorization changing only its context.
83
+ new_authorization = authorization.map(context: [
84
+ 'dashboard', 'controllers', 'billings', 'index'
85
+ ])
86
+
87
+ new_authorization.permissions.to?('visit') #=> false
88
+
89
+ authorization == new_authorization #=> false
90
+ ```
91
+
92
+ ## Original implementation
93
+
94
+ https://gist.github.com/serradura/7d51b979b90609d8601d0f416a9aa373
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Authorization
5
+ class Model
6
+ attr_reader :context, :permissions
7
+
8
+ def self.build(permissions:, context:, policies: {})
9
+ permissions_model =
10
+ Permissions.new(permissions, context: context.delete(:permissions))
11
+
12
+ self.new(context, permissions: permissions_model, policies: policies)
13
+ end
14
+
15
+ def initialize(context, permissions:, policies: {})
16
+ @context = context
17
+ @policies = {}
18
+ @policies_cache = {}
19
+ @permissions = Permissions[permissions]
20
+
21
+ add_policies(policies)
22
+ end
23
+
24
+ def map(context: nil, policies: nil)
25
+ if context.nil? && policies.nil?
26
+ raise ArgumentError, 'context or policies keywords args must be defined'
27
+ end
28
+
29
+ permissions_context = context || permissions.context
30
+
31
+ new_permissions =
32
+ Permissions.new(permissions.role, context: permissions_context)
33
+
34
+ self.class.new(context, permissions: new_permissions,
35
+ policies: policies || @policies)
36
+ end
37
+
38
+ def add_policy(key, policy_klass)
39
+ raise ArgumentError, 'key must be a Symbol' unless key.is_a?(Symbol)
40
+
41
+ default_ref = key == :default && policy_klass.is_a?(Symbol)
42
+
43
+ @policies[key] ||= default_ref ? policy_klass : Policy.type(policy_klass)
44
+
45
+ self
46
+ end
47
+
48
+ def add_policies(new_policies)
49
+ unless new_policies.is_a?(Hash)
50
+ raise ArgumentError, "policies must be a Hash (key => #{Policy.name})"
51
+ end
52
+
53
+ new_policies.each(&method(:add_policy))
54
+
55
+ self
56
+ end
57
+
58
+ def to(policy_key, subject: nil)
59
+ policy_klass = fetch_policy(policy_key)
60
+
61
+ return policy_klass.new(context, subject, permissions: permissions) if subject
62
+
63
+ return @policies_cache[policy_key] if @policies_cache[policy_key]
64
+
65
+ policy_klass.new(context, permissions: permissions).tap do |instance|
66
+ @policies_cache[policy_key] = instance if policy_klass != Policy
67
+ end
68
+ end
69
+
70
+ def policy(key = :default, subject: nil)
71
+ to(key, subject: subject)
72
+ end
73
+
74
+ private
75
+
76
+ def fetch_policy(policy_key)
77
+ data = @policies[policy_key]
78
+ value = data || @policies.fetch(:default, Policy)
79
+ value.is_a?(Symbol) ? fetch_policy(value) : value
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,53 @@
1
+ module Micro
2
+ module Authorization
3
+ module Permissions
4
+ module CheckRole
5
+ extend self
6
+
7
+ def call(context, role_permissions, required_features)
8
+ required_features
9
+ .all? { |feature| has_permission?(context, role_permissions[feature]) }
10
+ end
11
+
12
+ private
13
+
14
+ def has_permission?(context, role_permission)
15
+ return false if role_permission.nil?
16
+
17
+ if role_permission == false || role_permission == true
18
+ role_permission
19
+ elsif !(any = role_permission['any']).nil?
20
+ any
21
+ elsif only = role_permission['only']
22
+ check_feature_permission(only, context)
23
+ elsif except = role_permission['except']
24
+ !check_feature_permission(except, context)
25
+ else
26
+ raise NotImplementedError
27
+ end
28
+ end
29
+
30
+ def check_feature_permission(context_values, context)
31
+ Utils.values_as_downcased_strings(context_values).any? do |context_value|
32
+ Array(context_value.split('.')).all? { |permission| context.include?(permission) }
33
+ end
34
+ end
35
+ end
36
+
37
+ private_constant :CheckRole
38
+
39
+ class Checker
40
+ attr_reader :required_features
41
+
42
+ def initialize(role, features)
43
+ @role = role
44
+ @required_features = Utils.values_as_downcased_strings(features)
45
+ end
46
+
47
+ def context?(context)
48
+ CheckRole.call(context, @role, @required_features)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,33 @@
1
+ module Micro
2
+ module Authorization
3
+ module Permissions
4
+ class Model
5
+ attr_reader :role, :context
6
+
7
+ def initialize(role_permissions, context:)
8
+ @role = role_permissions.dup.freeze
9
+ @cache = {}
10
+ @context = Utils.values_as_downcased_strings(context).freeze
11
+ end
12
+
13
+ def to(features)
14
+ Permissions::Checker.new(@role, features)
15
+ end
16
+
17
+ def to?(features = nil)
18
+ has_permission_to = to(features)
19
+
20
+ cache_key = has_permission_to.required_features.inspect
21
+
22
+ return @cache[cache_key] unless @cache[cache_key].nil?
23
+
24
+ @cache[cache_key] = has_permission_to.context?(@context)
25
+ end
26
+
27
+ def to_not?(features = nil)
28
+ !to?(features)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'micro/authorization/permissions/checker'
4
+ require 'micro/authorization/permissions/model'
5
+
6
+ module Micro
7
+ module Authorization
8
+ module Permissions
9
+ def self.[](instance)
10
+ return instance if instance.is_a?(Permissions::Model)
11
+
12
+ raise ArgumentError, "#{instance.inspect} must be a #{self.name}"
13
+ end
14
+
15
+ def self.new(role_permissions, context: [])
16
+ Permissions::Model.new(role_permissions, context: context)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Authorization
5
+ class Policy
6
+ def self.type(klass)
7
+ return klass if klass < self
8
+
9
+ raise ArgumentError, "policy must be a #{self.name}"
10
+ end
11
+
12
+ def initialize(context, subject = nil, permissions: nil)
13
+ @context = context
14
+ @subject = subject
15
+ @permissions = permissions
16
+ end
17
+
18
+ def method_missing(method, *args, **keyargs, &block)
19
+ return false if method =~ /\?\z/
20
+ super(method)
21
+ end
22
+
23
+ private
24
+
25
+ def permissions; @permissions; end
26
+ def context; @context; end
27
+ def subject; @subject; end
28
+ def user
29
+ @user ||=
30
+ context.is_a?(Hash) ? context[:user] || context[:current_user] : context
31
+ end
32
+ alias_method :current_user, :user
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Authorization
5
+ module Utils
6
+ def self.values_as_downcased_strings(values)
7
+ Array(values).map { |value| String(value).downcase }
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Authorization
5
+ VERSION = '2.0.0'
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'micro/authorization/version'
4
+ require 'micro/authorization/utils'
5
+ require 'micro/authorization/permissions'
6
+ require 'micro/authorization/policy'
7
+ require 'micro/authorization/model'
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'authorization'
3
+ require 'micro/authorization'
@@ -0,0 +1,27 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'micro/authorization/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'u-authorization'
7
+ spec.version = Micro::Authorization::VERSION
8
+ spec.authors = ['Rodrigo Serradura']
9
+ spec.email = ['rodrigo.serradura@gmail.com']
10
+
11
+ spec.summary = 'Authorization library and role managment'
12
+ spec.description = 'Simple authorization library and role managment for Ruby.'
13
+ spec.homepage = 'https://github.com/serradura/u-authorization'
14
+ spec.license = 'MIT'
15
+
16
+ # Specify which files should be added to the gem when it is released.
17
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
18
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
+ end
21
+ spec.bindir = 'exe'
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.required_ruby_version = '>= 2.2.0'
26
+ spec.add_development_dependency 'rake', '~> 10.0'
27
+ end
metadata CHANGED
@@ -1,44 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: u-authorization
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Serradura
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2018-12-07 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2019-07-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '10.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '10.0'
13
27
  description: Simple authorization library and role managment for Ruby.
14
- email: rodrigo.serradura@gmail.com
28
+ email:
29
+ - rodrigo.serradura@gmail.com
15
30
  executables: []
16
31
  extensions: []
17
32
  extra_rdoc_files: []
18
33
  files:
19
- - authorization.rb
20
- - u-authorization.rb
21
- homepage: https://gist.github.com/serradura/7d51b979b90609d8601d0f416a9aa373
34
+ - ".gitignore"
35
+ - Gemfile
36
+ - Gemfile.lock
37
+ - README.md
38
+ - Rakefile
39
+ - lib/micro/authorization.rb
40
+ - lib/micro/authorization/model.rb
41
+ - lib/micro/authorization/permissions.rb
42
+ - lib/micro/authorization/permissions/checker.rb
43
+ - lib/micro/authorization/permissions/model.rb
44
+ - lib/micro/authorization/policy.rb
45
+ - lib/micro/authorization/utils.rb
46
+ - lib/micro/authorization/version.rb
47
+ - lib/u-authorization.rb
48
+ - u-authorization.gemspec
49
+ homepage: https://github.com/serradura/u-authorization
22
50
  licenses:
23
51
  - MIT
24
52
  metadata: {}
25
53
  post_install_message:
26
54
  rdoc_options: []
27
55
  require_paths:
28
- - "."
56
+ - lib
29
57
  required_ruby_version: !ruby/object:Gem::Requirement
30
58
  requirements:
31
59
  - - ">="
32
60
  - !ruby/object:Gem::Version
33
- version: 2.2.2
61
+ version: 2.2.0
34
62
  required_rubygems_version: !ruby/object:Gem::Requirement
35
63
  requirements:
36
64
  - - ">="
37
65
  - !ruby/object:Gem::Version
38
66
  version: '0'
39
67
  requirements: []
40
- rubyforge_project:
41
- rubygems_version: 2.7.7
68
+ rubygems_version: 3.0.1
42
69
  signing_key:
43
70
  specification_version: 4
44
71
  summary: Authorization library and role managment
data/authorization.rb DELETED
@@ -1,195 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Authorization
4
- VERSION = '1.4.0'
5
-
6
- MapValuesAsDowncasedStrings = -> (values) do
7
- Array(values).map { |value| String(value).downcase }
8
- end
9
-
10
- module CheckRolePermission
11
- extend self
12
-
13
- def call(context, role_permissions, required_features)
14
- required_features
15
- .all? { |feature| has_permission?(context, role_permissions[feature]) }
16
- end
17
-
18
- private
19
-
20
- def has_permission?(context, role_permission)
21
- return false if role_permission.nil?
22
-
23
- if role_permission == false || role_permission == true
24
- role_permission
25
- elsif !(any = role_permission['any']).nil?
26
- any
27
- elsif only = role_permission['only']
28
- check_feature_permission(only, context)
29
- elsif except = role_permission['except']
30
- !check_feature_permission(except, context)
31
- else
32
- raise NotImplementedError
33
- end
34
- end
35
-
36
- def check_feature_permission(context_values, context)
37
- MapValuesAsDowncasedStrings.(context_values).any? do |context_value|
38
- Array(context_value.split('.')).all? { |permission| context.include?(permission) }
39
- end
40
- end
41
- end
42
-
43
- class FeaturesPermissionChecker
44
- attr_reader :required_features
45
-
46
- def initialize(role, features)
47
- @role = role
48
- @required_features = MapValuesAsDowncasedStrings.(features)
49
- end
50
-
51
- def context?(context)
52
- CheckRolePermission.call(context, @role, @required_features)
53
- end
54
- end
55
-
56
- class Permissions
57
- attr_reader :role, :context
58
-
59
- def self.[](instance)
60
- return instance if instance.is_a?(Permissions)
61
-
62
- raise ArgumentError, "#{instance.inspect} must be a #{self.name}"
63
- end
64
-
65
- def initialize(role_permissions, context: [])
66
- @role = role_permissions.dup.freeze
67
- @cache = {}
68
- @context = MapValuesAsDowncasedStrings.(context).freeze
69
- end
70
-
71
- def to(features)
72
- FeaturesPermissionChecker.new(@role, features)
73
- end
74
-
75
- def to?(features = nil)
76
- has_permission_to = to(features)
77
-
78
- cache_key = has_permission_to.required_features.inspect
79
-
80
- return @cache[cache_key] unless @cache[cache_key].nil?
81
-
82
- @cache[cache_key] = has_permission_to.context?(@context)
83
- end
84
-
85
- def to_not?(features = nil)
86
- !to?(features)
87
- end
88
- end
89
-
90
- class Policy
91
- def self.type(klass)
92
- return klass if klass < self
93
-
94
- raise ArgumentError, "policy must be a #{self.name}"
95
- end
96
-
97
- def initialize(context, subject = nil, permissions: nil)
98
- @context = context
99
- @subject = subject
100
- @permissions = permissions
101
- end
102
-
103
- def method_missing(method, *args, **keyargs, &block)
104
- return false if method =~ /\?\z/
105
- super(method)
106
- end
107
-
108
- private
109
-
110
- def permissions; @permissions; end
111
- def context; @context; end
112
- def subject; @subject; end
113
- def user
114
- @user ||=
115
- context.is_a?(Hash) ? context[:user] || context[:current_user] : context
116
- end
117
- alias_method :current_user, :user
118
- end
119
-
120
- class Model
121
- attr_reader :user, :permissions
122
-
123
- def self.build(user, role, context: [], policies: {})
124
- permissions = Permissions.new(role, context: context)
125
-
126
- self.new(user, permissions: permissions, policies: policies)
127
- end
128
-
129
- def initialize(user, permissions:, policies: {})
130
- @user = user
131
- @policies = {}
132
- @policies_cache = {}
133
- @permissions = Permissions[permissions]
134
-
135
- add_policies(policies)
136
- end
137
-
138
- def map(context: nil, policies: nil)
139
- if context.nil? && policies.nil?
140
- raise ArgumentError, 'context or policies keywords args must be defined'
141
- end
142
-
143
- new_permissions =
144
- Permissions.new(permissions.role, context: context || @context)
145
-
146
- self.class.new(
147
- user, permissions: new_permissions, policies: policies || @policies
148
- )
149
- end
150
-
151
- def add_policy(key, policy_klass)
152
- raise ArgumentError, 'key must be a Symbol' unless key.is_a?(Symbol)
153
-
154
- default_ref = key == :default && policy_klass.is_a?(Symbol)
155
-
156
- @policies[key] ||= default_ref ? policy_klass : Policy.type(policy_klass)
157
-
158
- self
159
- end
160
-
161
- def add_policies(new_policies)
162
- unless new_policies.is_a?(Hash)
163
- raise ArgumentError, "policies must be a Hash (key => #{Policy.name})"
164
- end
165
-
166
- new_policies.each(&method(:add_policy))
167
-
168
- self
169
- end
170
-
171
- def to(policy_key, subject: nil)
172
- policy_klass = fetch_policy(policy_key)
173
-
174
- return policy_klass.new(user, subject, permissions: permissions) if subject
175
-
176
- return @policies_cache[policy_key] if @policies_cache[policy_key]
177
-
178
- policy_klass.new(user, permissions: permissions).tap do |instance|
179
- @policies_cache[policy_key] = instance if policy_klass != Policy
180
- end
181
- end
182
-
183
- def policy(key = :default, subject: nil)
184
- to(key, subject: subject)
185
- end
186
-
187
- private
188
-
189
- def fetch_policy(policy_key)
190
- data = @policies[policy_key]
191
- value = data || @policies.fetch(:default, Policy)
192
- value.is_a?(Symbol) ? fetch_policy(value) : value
193
- end
194
- end
195
- end