shark-permissions-core 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []