shark-permissions-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c8fe97d4806a76b363e8b8654cda1df78b26c2a81272d8592f2e457e203a2b34
4
+ data.tar.gz: 7e50f496ab65ca30fabbea8f2bf69b3a98ca65f0ec2a159ae353075a5cddd36e
5
+ SHA512:
6
+ metadata.gz: 24a28fc4b1420a52f9b0083b92b704c28cb957f23248014d1f989385e5c97569373d902477f80924e819439e388bfa2cbeff238feb3de40f3ca391493a222de2
7
+ data.tar.gz: 9ae791ae0f3766df9a0d0e96839104ce10097b70f002fe8e5ad6d5f53d8fa56483b6a1cfcb0c649f68634317cf5f00c1ecd364815368a33c1c1060fd9a9850ae
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ gems.locked
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --require spec_helper
3
+ --format progress
4
+ --profile
data/.rubocop.yml ADDED
@@ -0,0 +1,37 @@
1
+ # RuboCop will start looking for the configuration file in the directory
2
+ # where the inspected file is and continue its way up to the root directory.
3
+ #
4
+ # See https://docs.rubocop.org/rubocop/configuration
5
+
6
+ AllCops:
7
+ TargetRubyVersion: 2.3
8
+ Exclude:
9
+ - 'bin/*'
10
+ - 'spec/**/*'
11
+
12
+ Layout/LineLength:
13
+ Max: 100
14
+
15
+ Lint/RaiseException:
16
+ Enabled: true
17
+ Lint/StructNewOverride:
18
+ Enabled: true
19
+
20
+ Metrics/AbcSize:
21
+ Enabled: true
22
+ Max: 20
23
+ Metrics/ClassLength:
24
+ Enabled: false
25
+ Metrics/MethodLength:
26
+ Enabled: false
27
+ Metrics/ModuleLength:
28
+ Enabled: false
29
+
30
+ Style/Documentation:
31
+ Enabled: false
32
+ Style/HashEachMethods:
33
+ Enabled: true
34
+ Style/HashTransformKeys:
35
+ Enabled: true
36
+ Style/HashTransformValues:
37
+ Enabled: true
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ ## Changelog
2
+
3
+ #### 0.1.0
4
+ - initial
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Jörgen Dahlke
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1 @@
1
+ # shark-permissions-core
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
data/gems.rb ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in shark-permissions-core.gemspec
6
+ gemspec
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shark
4
+ module Permissions
5
+ class Changes
6
+ attr_reader :effect
7
+ attr_reader :privileges
8
+
9
+ def initialize
10
+ @privileges = {}
11
+ @effect = {}
12
+ end
13
+
14
+ def add(field, old_value, new_value)
15
+ return if old_value == new_value
16
+
17
+ instance_variable_set("@#{field}", { old: old_value, new: old_value })
18
+ end
19
+
20
+ def add_privilege(key, old_value, new_value)
21
+ @privileges[:old] ||= {}
22
+ @privileges[:new] ||= {}
23
+ @privileges[:old][key] = old_value
24
+ @privileges[:new][key] = new_value
25
+ end
26
+
27
+ def present?
28
+ @effect.present? || privileges.present?
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,266 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shark
4
+ module Permissions
5
+ class List
6
+ delegate :[], :[]=, :empty?, :size, to: :rules
7
+ delegate :key?, :keys, :values, :each, :each_with_object, to: :rules
8
+
9
+ attr_reader :rules
10
+
11
+ def initialize(rules = {})
12
+ case rules
13
+ when Shark::Permissions::List
14
+ @rules = to_permission_rules(rules.as_json)
15
+ when Hash
16
+ @rules = to_permission_rules(rules)
17
+ else
18
+ raise ArgumentError, 'Rules must be a subtype of Hash'
19
+ end
20
+ end
21
+
22
+ # @return [Boolean]
23
+ # @api public
24
+ def ==(other)
25
+ rules == other.rules
26
+ end
27
+
28
+ # @api public
29
+ def <<(rule)
30
+ @rules[rule.resource] = rule
31
+ end
32
+
33
+ # @return [Array]
34
+ # @api public
35
+ def map(&block)
36
+ rules.values.map(&block)
37
+ end
38
+
39
+ def changes
40
+ changed_rules = rules.select { |_key, rule| rule.changed? }.to_h
41
+ self.class.new(changed_rules)
42
+ end
43
+
44
+ # Returns a new Permissions::List with same rules.
45
+ #
46
+ # @return [Permissions::List]
47
+ # @api public
48
+ def clone
49
+ cloned_rules = {}
50
+ rules.each { |k, rule| cloned_rules[k] = rule.clone }
51
+
52
+ self.class.new(cloned_rules)
53
+ end
54
+
55
+ # Returns a new Permissions::List without any rules that have no privileges.
56
+ #
57
+ # @return [Permissions::List]
58
+ # @api public
59
+ def compact
60
+ new_rules = {}
61
+
62
+ rules.keys.sort.each do |k|
63
+ rule = rules[k].clone
64
+ new_rules[k] = rule unless rule.empty?
65
+ end
66
+
67
+ self.class.new(new_rules)
68
+ end
69
+
70
+ def delete(key)
71
+ case key
72
+ when String
73
+ rules.delete(key)
74
+ when Permissions::Rule
75
+ rules.delete(key.resource)
76
+ else
77
+ raise ArgumentError, 'Argument must be a String or Permissions::Rule'
78
+ end
79
+ end
80
+
81
+ def select(names)
82
+ filtered_rules = {}
83
+
84
+ Array(names).each do |filter_name|
85
+ filter_resource = Permissions::Resource.new(filter_name)
86
+ rules.each do |name, rule|
87
+ next unless filter_resource.super_resource_of?(name)
88
+
89
+ filtered_rules[rule.resource] = rule.clone
90
+ end
91
+ end
92
+
93
+ self.class.new(filtered_rules)
94
+ end
95
+ alias filter select
96
+
97
+ def reject(names)
98
+ filtered_rules = {}
99
+
100
+ rejected_keys = select(names).keys
101
+ rules.each do |name, rule|
102
+ next if rejected_keys.include?(name)
103
+
104
+ filtered_rules[rule.resource] = rule.clone
105
+ end
106
+
107
+ self.class.new(filtered_rules)
108
+ end
109
+
110
+ def merge(other_list)
111
+ clone.merge!(other_list)
112
+ end
113
+
114
+ def merge!(other_list)
115
+ other_list.each do |key, other_rule|
116
+ rules[key] = Permissions::Rule.new(resource: key) unless rules.key?(key)
117
+ rules[key].update(other_rule)
118
+ end
119
+
120
+ self
121
+ end
122
+
123
+ # @example:
124
+ # list.privileges(:paragraph, :contracts)
125
+ # # => { 'admin' => true, 'edit' => true }
126
+ #
127
+ # @return [Hash]
128
+ # @api public
129
+ def privileges(*resources)
130
+ matching_resources = matching_resources(*resources)
131
+ privileges_set = Set.new
132
+
133
+ matching_resources.each do |name|
134
+ rule = rules[name]
135
+
136
+ next unless rule
137
+
138
+ case rule.effect
139
+ when 'ALLOW'
140
+ privileges_set.merge(rule.privileges_as_array)
141
+ when 'DENY'
142
+ privileges.subtract(rule.privileges_as_array)
143
+ end
144
+ end
145
+
146
+ privileges_set.map { |k| [k, true] }.to_h
147
+ end
148
+
149
+ # @example:
150
+ # list.authorized?(:admin, :paragraph, :contracts)
151
+ # # => true
152
+ # list.authorized?([:read, :write], :datenraum, :berlin)
153
+ # # => false
154
+ #
155
+ # @return [Boolean]
156
+ # @api public
157
+ def authorized?(privilege, *resources)
158
+ if privilege == Shark::Permissions.any_matcher
159
+ privileges(*resources).present?
160
+ else
161
+ privilege_array = Array(privilege).map(&:to_s)
162
+ privileges_for_resource = privileges(*resources)
163
+ privilege_array.any? { |p| privileges_for_resource.fetch(p, false) }
164
+ end
165
+ end
166
+
167
+ # @example:
168
+ # list.subresource_authorized?(:admin, :paragraph, :contracts)
169
+ # # => true
170
+ #
171
+ # @return [Boolean]
172
+ # @api public
173
+ def subresource_authorized?(privilege, *resources)
174
+ authorized?(privilege, *resources, Shark::Permissions.any_matcher)
175
+ end
176
+
177
+ # Correctly set privileges for subresources.
178
+ #
179
+ # @return [Permissions::List]
180
+ # @api public
181
+ def set_inherited_privileges!
182
+ rules.each do |resource, rule|
183
+ privileges = privileges(resource)
184
+ privileges.each { |k, v| rule.privileges[k] = v if rule.privileges.key?(k) }
185
+ end
186
+
187
+ self
188
+ end
189
+
190
+ # Returns new list without inherited privileges and empty rules.
191
+ #
192
+ # @return [Permissions::List]
193
+ # @api public
194
+ def remove_inherited_rules
195
+ new_list = self.class.new({})
196
+
197
+ rules.keys.sort.each do |name|
198
+ new_rule = Permissions::Rule.new(resource: name)
199
+ parent = new_rule.parent
200
+
201
+ rules[name].privileges.each do |k, v|
202
+ new_rule.privileges[k] = v if v && !new_list.authorized?(k, parent)
203
+ end
204
+
205
+ new_list << new_rule unless new_rule.empty?
206
+ end
207
+
208
+ new_list
209
+ end
210
+
211
+ # @return [Hash]
212
+ # @api public
213
+ def as_json(*args)
214
+ rules.as_json(*args)
215
+ end
216
+
217
+ # For Deserializaton
218
+ #
219
+ # @return [Shark::Permissions::List]
220
+ # @api public
221
+ def self.load(json)
222
+ if json.nil?
223
+ new
224
+ else
225
+ new(JSON.parse(json))
226
+ end
227
+ end
228
+
229
+ # For Serializaton
230
+ #
231
+ # @return [String]
232
+ # @api public
233
+ def self.dump(list)
234
+ list&.to_json
235
+ end
236
+
237
+ private
238
+
239
+ def to_permission_rules(rules)
240
+ return {} if rules.blank?
241
+
242
+ if rules.values.first.is_a?(Permissions::Rule)
243
+ # do nothing
244
+ else
245
+ rules = rules.map { |k, v| [k.to_s, Permissions::Rule.new(v)] }
246
+ rules = Hash[rules]
247
+ end
248
+
249
+ rules
250
+ end
251
+
252
+ def matching_resources(*resources)
253
+ resource = Resource.new(resources)
254
+ result = resource.ancestors_and_self
255
+
256
+ if resource.wildcard?
257
+ result = resource.ancestors
258
+ subresources = rules.keys.select { |name| resource.super_resource_of?(name) }
259
+ result.concat(subresources)
260
+ end
261
+
262
+ result
263
+ end
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shark
4
+ module Permissions
5
+ class Resource
6
+ attr_reader :name, :parts
7
+
8
+ def initialize(value)
9
+ @name = case value
10
+ when String
11
+ value
12
+ when Array
13
+ value.map(&:to_s).join(Shark::Permissions.delimiter)
14
+ end
15
+ @parts = @name.split(Shark::Permissions.delimiter)
16
+ end
17
+
18
+ def ancestors_and_self
19
+ names = []
20
+ parts.each_with_index do |_, i|
21
+ names << parts[0..i].join(Shark::Permissions.delimiter)
22
+ end
23
+
24
+ names
25
+ end
26
+
27
+ def ancestors
28
+ ancestors_and_self[0..-2]
29
+ end
30
+
31
+ def parent
32
+ parent_name = parts[0..-2].join(Shark::Permissions.delimiter)
33
+ parent_name.presence
34
+ end
35
+
36
+ def subresource_of?(value)
37
+ return true if name == value
38
+
39
+ regexp = value.gsub('*', '[a-z\-_0-9]*')
40
+ "#{name}::".match(/\A#{regexp}::/).present?
41
+ end
42
+
43
+ def super_resource_of?(value)
44
+ return true if name == value
45
+
46
+ regexp = name.gsub('*', '[a-z\-_0-9]*')
47
+ "#{value}::".match(/\A#{regexp}::/).present?
48
+ end
49
+
50
+ def wildcard?
51
+ parts.last == Shark::Permissions.any_matcher
52
+ end
53
+
54
+ def to_s
55
+ name
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shark
4
+ module Permissions
5
+ class Rule
6
+ attr_accessor :resource, :effect, :privileges, :title
7
+ attr_reader :changes
8
+
9
+ delegate :parent, to: :resource_model
10
+
11
+ def initialize(args)
12
+ symbol_args = args.symbolize_keys
13
+
14
+ @resource = symbol_args.fetch(:resource)
15
+ @privileges = symbol_args[:privileges] || {}
16
+ normalize_privileges(@privileges)
17
+ @effect = symbol_args[:effect] || 'ALLOW'
18
+ @title = symbol_args[:title]
19
+ @changes = Changes.new
20
+ end
21
+
22
+ def update(other)
23
+ if resource != other.resource
24
+ raise ArgumentError, "Trying to update different resource: got #{other.resource}, " \
25
+ "but expected #{resource}"
26
+ end
27
+
28
+ other.privileges.each do |k, v|
29
+ next if privileges[k] == v
30
+
31
+ old = privileges[k]
32
+ privileges[k] = v
33
+
34
+ next if old == 'inherited'
35
+
36
+ changes.add_privilege(k, old || false, v)
37
+ end
38
+
39
+ self
40
+ end
41
+
42
+ def changed?
43
+ changes.present?
44
+ end
45
+
46
+ def clone
47
+ self.class.new(as_json)
48
+ end
49
+
50
+ def empty?
51
+ privileges.blank?
52
+ end
53
+
54
+ def resource_model
55
+ Resource.new(resource)
56
+ end
57
+
58
+ def privileges_as_array
59
+ privileges.select { |_, v| v == true }.keys
60
+ end
61
+
62
+ # @return Boolean
63
+ # @api public
64
+ def ==(other)
65
+ resource == other.resource &&
66
+ effect == other.effect &&
67
+ privileges == other.privileges
68
+ end
69
+
70
+ def as_json(*_args)
71
+ json = {
72
+ 'resource' => resource,
73
+ 'privileges' => privileges,
74
+ 'effect' => effect,
75
+ 'parent' => parent
76
+ }
77
+ json['title'] = title if title.present?
78
+
79
+ json
80
+ end
81
+
82
+ private
83
+
84
+ def normalize_privileges(privileges)
85
+ privileges.each do |k, v|
86
+ privileges[k] = case v
87
+ when 'inherited'
88
+ 'inherited'
89
+ when true, 'true', 1
90
+ true
91
+ when false, 'false', 0
92
+ false
93
+ else
94
+ false
95
+ end
96
+ end
97
+
98
+ @privileges = privileges.stringify_keys
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shark
4
+ module Permissions
5
+ module Core
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ # rubocop:disable Naming/FileName
2
+ # frozen_string_literal: true
3
+
4
+ # rubocop:enable Naming/FileName
5
+
6
+ require 'shark_permissions_core'
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/all'
4
+
5
+ require 'shark/permissions/changes'
6
+ require 'shark/permissions/resource'
7
+ require 'shark/permissions/rule'
8
+ require 'shark/permissions/list'
9
+
10
+ module Shark
11
+ module Permissions
12
+ mattr_accessor :any_matcher, :delimiter
13
+
14
+ def self.configure
15
+ yield self
16
+ end
17
+
18
+ configure do |config|
19
+ config.delimiter = '::'
20
+ config.any_matcher = '*'
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'shark/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'shark-permissions-core'
9
+ spec.version = Shark::Permissions::Core::VERSION
10
+ spec.authors = ['Joergen Dahlke']
11
+ spec.email = ['joergen.dahlke@gmail.com']
12
+
13
+ spec.summary = 'Core classes for Shark permissions'
14
+ spec.description = 'Basic functionality to work with shark permissions'
15
+ spec.homepage = 'https://github.com/jdahlke/shark-permissions-core'
16
+ spec.license = 'MIT'
17
+
18
+ spec.metadata['homepage_uri'] = spec.homepage
19
+ spec.metadata['source_code_uri'] = 'https://github.com/jdahlke/shark-permissions-core'
20
+ spec.metadata['changelog_uri'] = 'https://github.com/jdahlke/shark-permissions-core/blob/develop/CHANGELOG.md'
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(.github|bin|spec)/}) }
26
+ end
27
+ spec.bindir = 'exe'
28
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ['lib']
30
+ spec.required_ruby_version = '>= 2.3'
31
+
32
+ spec.add_dependency 'activesupport'
33
+
34
+ spec.add_development_dependency 'bundler', '~> 2.0'
35
+ spec.add_development_dependency 'rake'
36
+ spec.add_development_dependency 'rspec', '~> 3.9.0'
37
+ spec.add_development_dependency 'rubocop', '0.81.0'
38
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shark-permissions-core
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Joergen Dahlke
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-09-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 3.9.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 3.9.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 0.81.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.81.0
83
+ description: Basic functionality to work with shark permissions
84
+ email:
85
+ - joergen.dahlke@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".rubocop.yml"
93
+ - CHANGELOG.md
94
+ - LICENSE
95
+ - README.md
96
+ - Rakefile
97
+ - gems.rb
98
+ - lib/shark-permissions-core.rb
99
+ - lib/shark/permissions/changes.rb
100
+ - lib/shark/permissions/list.rb
101
+ - lib/shark/permissions/resource.rb
102
+ - lib/shark/permissions/rule.rb
103
+ - lib/shark/version.rb
104
+ - lib/shark_permissions_core.rb
105
+ - shark-permissions-core.gemspec
106
+ homepage: https://github.com/jdahlke/shark-permissions-core
107
+ licenses:
108
+ - MIT
109
+ metadata:
110
+ homepage_uri: https://github.com/jdahlke/shark-permissions-core
111
+ source_code_uri: https://github.com/jdahlke/shark-permissions-core
112
+ changelog_uri: https://github.com/jdahlke/shark-permissions-core/blob/develop/CHANGELOG.md
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '2.3'
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubygems_version: 3.1.6
129
+ signing_key:
130
+ specification_version: 4
131
+ summary: Core classes for Shark permissions
132
+ test_files: []