rushiro 1.0.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.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
@@ -0,0 +1,161 @@
1
+ require 'date'
2
+ require 'rspec/core/rake_task'
3
+
4
+ #############################################################################
5
+ #
6
+ # Helper functions
7
+ #
8
+ #############################################################################
9
+
10
+ def name
11
+ @name ||= Dir['*.gemspec'].first.split('.').first
12
+ end
13
+
14
+ def version
15
+ line = File.read("lib/#{name}/version.rb")[/^\s*VERSION\s*=\s*.*/]
16
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
17
+ end
18
+
19
+ def date
20
+ Date.today.to_s
21
+ end
22
+
23
+ def rubyforge_project
24
+ name
25
+ end
26
+
27
+ def gemspec_file
28
+ "#{name}.gemspec"
29
+ end
30
+
31
+ def gem_file
32
+ "#{name}-#{version}.gem"
33
+ end
34
+
35
+ def replace_header(head, header_name, provider = nil)
36
+ if provider
37
+ value = send(provider)
38
+ else
39
+ value = "'#{send(header_name)}'"
40
+ end
41
+
42
+ provider ||= header_name
43
+ head.sub!(/(\.#{header_name}\s*= ).*/) { "#{$1}#{value}"}
44
+ end
45
+
46
+ def platform
47
+ jruby? ? '-java' : ''
48
+ end
49
+
50
+ def platform_dependant_gem_file
51
+ "#{name}-#{version}#{platform}.gem"
52
+ end
53
+
54
+ def platform_dependent_version
55
+ "'#{version}#{platform}'"
56
+ end
57
+
58
+ def jruby?
59
+ RUBY_PLATFORM.to_s == 'java'
60
+ end
61
+
62
+ def trim_array_ends array
63
+ array.shift
64
+ array.pop
65
+ array
66
+ end
67
+
68
+ #############################################################################
69
+ #
70
+ # Custom tasks
71
+ #
72
+ #############################################################################
73
+
74
+ default_rspec_opts = %w[--colour --format Fuubar]
75
+
76
+ desc "Run all examples"
77
+ RSpec::Core::RakeTask.new(:spec) do |t|
78
+ t.rspec_opts = default_rspec_opts
79
+ end
80
+
81
+ #############################################################################
82
+ #
83
+ # Packaging tasks
84
+ #
85
+ #############################################################################
86
+
87
+ def built_gem
88
+ @built_gem ||= Dir["#{name}*.gem"].first
89
+ end
90
+
91
+ desc "Create tag v#{platform_dependent_version} and build and push #{platform_dependant_gem_file} to Rubygems"
92
+ task :release => :build do
93
+ unless `git branch` =~ /^\* master$/
94
+ puts "You must be on the master branch to release!"
95
+ exit!
96
+ end
97
+
98
+ sh "git commit --allow-empty -a -m 'Release #{platform_dependent_version}'"
99
+ sh "git tag v#{platform_dependent_version}"
100
+ sh "git push origin master"
101
+ sh "git push origin v#{platform_dependent_version}"
102
+
103
+ command = "gem push pkg/#{platform_dependant_gem_file}"
104
+
105
+ if jruby?
106
+ puts "--------------------------------------------------------------------------------------"
107
+ puts "can't push to rubygems using jruby at the moment, so switch to mri and run: #{command}"
108
+ puts "--------------------------------------------------------------------------------------"
109
+ else
110
+ sh command
111
+ end
112
+ end
113
+
114
+ desc "Build #{platform_dependant_gem_file} into the pkg directory"
115
+ task :build => :gemspec do
116
+ sh "mkdir -p pkg"
117
+ sh "gem build #{gemspec_file}"
118
+ sh "mv #{built_gem} pkg"
119
+ end
120
+
121
+ desc "Generate #{gemspec_file}"
122
+ task :gemspec => :validate do
123
+ # read spec file and split out manifest section
124
+ spec = File.read(gemspec_file)
125
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
126
+
127
+ # replace name version and date
128
+ replace_header(head, :name)
129
+ replace_header(head, :version)
130
+ replace_header(head, :date)
131
+ #comment this out if your rubyforge_project has a different name
132
+ #replace_header(head, :rubyforge_project)
133
+
134
+ # determine file list from git ls-files
135
+ files = `git ls-files`.
136
+ split("\n").
137
+ sort.
138
+ reject { |file| file =~ /^\./ }.
139
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
140
+ map { |file| " #{file}" }.
141
+ join("\n")
142
+
143
+ # piece file back together and write
144
+ manifest = " s.files = %w[\n#{files}\n ]\n"
145
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
146
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
147
+ puts "Updated #{gemspec_file}"
148
+ end
149
+
150
+ desc "Validate #{gemspec_file}"
151
+ task :validate do
152
+ libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
153
+ unless libfiles.empty?
154
+ puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
155
+ exit!
156
+ end
157
+ unless Dir['VERSION*'].empty?
158
+ puts "A `VERSION` file at root level violates Gem best practices."
159
+ exit!
160
+ end
161
+ end
@@ -0,0 +1,4 @@
1
+ %w[permission permissions access_levels access_control_hash deny_based_control allow_based_control].each do |file|
2
+ require "rushiro/#{file}"
3
+ end
4
+
@@ -0,0 +1,50 @@
1
+ module Rushiro
2
+ GSEP = '|'
3
+ FSEP = ','
4
+ class AccessControlHash
5
+ attr_reader :allows, :denies, :original, :dirty
6
+ def initialize(hash)
7
+ @allows = AccessLevels.new(hash[:allows] || {})
8
+ @denies = AccessLevels.new(hash[:denies] || {})
9
+ @dirty = false
10
+ @original = hash
11
+ end
12
+
13
+ def permitted?(perm)
14
+ # virtual
15
+ end
16
+
17
+ def add_permission(perm) #as string "allow|individual|domain(|action(|instance))"
18
+ grant, rest = perm.split(GSEP, 2)
19
+ case grant
20
+ when 'allows'
21
+ @allows.add_permission(rest) and @dirty = true
22
+ when 'denies'
23
+ @denies.add_permission(rest) and @dirty = true
24
+ else
25
+ raise ArgumentError.new("Could not add permission for type: #{grant} of #{perm}")
26
+ end
27
+ end
28
+
29
+ def remove_permission(perm) #as string "allow|individual|domain(|action(|instance))"
30
+ grant, rest = perm.split(GSEP, 2)
31
+ case grant
32
+ when 'allows'
33
+ @allows.remove_permission(rest) and @dirty = true
34
+ when 'denies'
35
+ @denies.remove_permission(rest) and @dirty = true
36
+ else
37
+ raise ArgumentError.new("Could not remove permission for type: #{grant} of #{perm}")
38
+ end
39
+ end
40
+
41
+ def serialize
42
+ unless @dirty
43
+ @original
44
+ else
45
+ Hash[:allows, @allows.serialize, :denies, @denies.serialize]
46
+ end
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,45 @@
1
+ module Rushiro
2
+ class AccessLevels
3
+ attr_reader :individual, :organization, :musicglue
4
+ def initialize(hash)
5
+ @individual = Permissions.new(hash[:individual] || [])
6
+ @organization = Permissions.new(hash[:organization] || [])
7
+ @system = Permissions.new(hash[:system] || [])
8
+ end
9
+
10
+ def permitted?(perm)
11
+ @system.permitted?(perm) || @organization.permitted?(perm) || @individual.permitted?(perm)
12
+ end
13
+
14
+ def add_permission(perm)
15
+ level, rest = perm.split(GSEP, 2)
16
+ case level
17
+ when 'individual'
18
+ @individual.add_permission(rest)
19
+ when 'organization'
20
+ @organization.add_permission(rest)
21
+ when 'system'
22
+ @system.add_permission(rest)
23
+ else
24
+ raise ArgumentError.new("Could not add permission for level: #{level} of #{level}#{SEP}#{perm}")
25
+ end
26
+ end
27
+
28
+ def remove_permission(perm)
29
+ level, rest = perm.split(GSEP, 2)
30
+ case level
31
+ when 'individual'
32
+ @individual.remove_permission(rest)
33
+ when 'organization'
34
+ @organization.remove_permission(rest)
35
+ when 'system'
36
+ @system.remove_permission(rest)
37
+ else
38
+ raise ArgumentError.new("Could not remove permission for level: #{level} of #{level}#{SEP}#{rest}")
39
+ end
40
+ end
41
+ def serialize
42
+ Hash[:individual, @individual.serialize, :organization, @organization.serialize, :system, @system.serialize]
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,8 @@
1
+ module Rushiro
2
+ class AllowBasedControl < AccessControlHash
3
+ def permitted?(perm)
4
+ return false if !@dirty && @original.empty?
5
+ @allows.permitted?(perm)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ module Rushiro
2
+ class DenyBasedControl < AccessControlHash
3
+ def permitted?(perm)
4
+ return true if !@dirty && @original.empty?
5
+ return false if @denies.permitted?(perm)
6
+ true
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,41 @@
1
+ module Rushiro
2
+ class Permission
3
+ attr_reader :parts, :original, :size
4
+
5
+ WildCard = :*
6
+
7
+ def initialize(permission)
8
+ raise ArgumentError.new("Invalid permission, empty string") if permission.empty?
9
+ @original = permission
10
+ @parts = permission.split(GSEP).map {|part| part.split(FSEP).map(&:to_sym)}
11
+ @size = @parts.size
12
+ end
13
+
14
+ def implied?(perm)
15
+ if perm.include?(FSEP)
16
+ msg = "Field separator: #{FSEP} used in input. Be specific, one of domain, action or instance per section."
17
+ raise ArgumentError.new(msg)
18
+ end
19
+ array = perm.split(GSEP).map(&:to_sym)
20
+ array.each_with_index do |part_sym, idx|
21
+ # If the permission has less parts than the permission being tested, everything after the number
22
+ # of parts contained in this permission is automatically implied, so return true
23
+ break if @size.pred < idx
24
+ return false if (@parts[idx] & [WildCard, part_sym]).empty?
25
+ end
26
+ true
27
+ end
28
+
29
+ def ==(other)
30
+ return false unless @size == other.size
31
+ @parts.each_with_index do |part, idx|
32
+ return false unless (part - other.parts[idx]).empty?
33
+ end
34
+ true
35
+ end
36
+
37
+ def serialize
38
+ @parts.map { |part| part.map(&:to_s).join(FSEP) }.join(GSEP)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ module Rushiro
2
+ class Permissions
3
+ attr_reader :permissions
4
+ def initialize(entries)
5
+ @permissions = entries.map do |permission|
6
+ Permission.new(permission)
7
+ end || []
8
+ end
9
+
10
+ def permitted?(perm)
11
+ return nil if @permissions.empty?
12
+ @permissions.any? { |permission| permission.implied?(perm) }
13
+ end
14
+
15
+ def add_permission(perm)
16
+ to_add = Permission.new(perm)
17
+ return false if @permissions.any?{|_p| _p == to_add}
18
+ @permissions << to_add
19
+ true
20
+ end
21
+
22
+ def remove_permission(perm)
23
+ !!@permissions.delete(Permission.new(perm))
24
+ end
25
+
26
+ def serialize
27
+ @permissions.map(&:serialize)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module Rushiro
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,108 @@
1
+ # Description
2
+
3
+ ## Summary
4
+
5
+ Rushiro takes a source hash (of permissions definitions) and gives you an object that
6
+ you use to test for permitted access.
7
+
8
+ ## Strategies
9
+
10
+ - Deny all allow some, use the AllowBasedControl class
11
+ - Allow all deny some, use the DenyBasedControl class
12
+
13
+ ## Permissions
14
+
15
+ Manages explicit permissions. It lets you define 'allows' or 'denies' permissions.
16
+ You can define level based permissions i.e. individual, organization or
17
+ system. Organization or system levels are meant for short term overrides.
18
+ Set long term permissions at the individual level.
19
+
20
+ For more information on this check out [Apache Shiro permissions][shiro_p].
21
+
22
+ The permission part has three pipe separated sections, domain, action and instance:
23
+
24
+ - Domain - aka resources, e.g. webpages, db entities, domain models or services
25
+ - Action - these entries are from the set of actions available for the domain
26
+ - Instance - these entries are 'labels' you have given to instances of the domain,
27
+ e.g. a particular webpage, a uuid or unique id of a db record
28
+ Note: Multiple comma separated entries are allowed, as is the * wildcard
29
+
30
+
31
+ example: this hash is processed into a hierarchy of objects
32
+ ``` ruby
33
+ perm = {
34
+ allows: {
35
+ individual: [
36
+ "feature|create,read,update|feat-x",
37
+ "page|*|posts",
38
+ "company|read"
39
+ "company|update|5b90a720-e6b0-012e-dc18-782bcb979e60"
40
+ ],
41
+ organization: [],
42
+ system: []
43
+ },
44
+ denies: {}
45
+ }
46
+
47
+ access_control = AllowBasedControl.new(perm)
48
+ access_control.permitted?("company|read|5b90a720-e6b0-012e-dc18-782bcb979e60") ==> true
49
+ access_control.permitted?("feature|delete|feat-x") ==> false
50
+ ```
51
+
52
+ Read the specs for more usage examples.
53
+
54
+ ## Source Hash
55
+
56
+ Typically the source hash will be stored in a document database directly as a hash field
57
+ in the User record (see [Subject][shiro_s]). In most cases this would be the current_users db document.
58
+
59
+ ## Authorization
60
+
61
+ The permitted?(permission) method is called to check authorization. The string you pass
62
+ in is obtained from metadata in your application. It should be specific, exact domain,
63
+ action and instance.
64
+
65
+ # Future
66
+
67
+ ## Roles
68
+
69
+ A chaining mechanism is needed. References to other Rushiro control objects are assigned
70
+ to the current_user's Rushiro control and permission is tested through the chain.
71
+
72
+ ## Mixed Mode
73
+
74
+ The jury is still out on whether it is practical to have a mixture of allows and denies.
75
+
76
+ # Development
77
+
78
+ * Source hosted at [GitHub][repo]
79
+ * Report issues/Questions/Feature requests on [GitHub Issues][issues]
80
+
81
+ Pull requests are very welcome! Make sure your patches are well tested.
82
+ Ideally create a topic branch for every separate change you make.
83
+
84
+ # License and Authors
85
+
86
+ Author:: Guy Boertje (<guyboertje@gmail.com>)
87
+ Author:: Lee Henson (<leemhenson@gmail.com>)
88
+
89
+ Contributors:: https://github.com/guyboertje/rushiro/contributors
90
+
91
+ Copyright (c) 2011, Guy Boertje
92
+
93
+ Licensed under the Apache License, Version 2.0 (the "License");
94
+ you may not use this file except in compliance with the License.
95
+ You may obtain a copy of the License at
96
+
97
+ http://www.apache.org/licenses/LICENSE-2.0
98
+
99
+ Unless required by applicable law or agreed to in writing, software
100
+ distributed under the License is distributed on an "AS IS" BASIS,
101
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
102
+ See the License for the specific language governing permissions and
103
+ limitations under the License.
104
+
105
+ [repo]: https://github.com/guyboertje/rushiro
106
+ [issues]: https://github.com/guyboertje/rushiro/issues
107
+ [shiro_p]: http://shiro.apache.org/permissions.html
108
+ [shiro_s]: http://shiro.apache.org/subject.html
@@ -0,0 +1,37 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'rushiro'
3
+ s.version = '1.0.0'
4
+ s.date = '2011-11-03'
5
+ s.platform = Gem::Platform::RUBY
6
+ s.authors = ['Lee Henson', 'Guy Boertje']
7
+ s.email = ['lee.m.henson@gmail.com', 'guyboertje@gmail.com']
8
+ s.homepage = "http://github.com/guyboertje/rushiro"
9
+ s.summary = %q{Explicit permissions inspired by Apache Shiro}
10
+ s.description = %q{}
11
+
12
+ # = MANIFEST =
13
+ s.files = %w[
14
+ Gemfile
15
+ Rakefile
16
+ lib/rushiro.rb
17
+ lib/rushiro/access_control_hash.rb
18
+ lib/rushiro/access_levels.rb
19
+ lib/rushiro/allow_based_control.rb
20
+ lib/rushiro/deny_based_control.rb
21
+ lib/rushiro/permission.rb
22
+ lib/rushiro/permissions.rb
23
+ lib/rushiro/version.rb
24
+ readme.md
25
+ rushiro.gemspec
26
+ spec/access_control_spec.rb
27
+ spec/spec_helper.rb
28
+ ]
29
+ # = MANIFEST =
30
+
31
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
32
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
33
+
34
+ s.add_development_dependency 'awesome_print', '~> 0.4.0'
35
+ s.add_development_dependency 'fuubar', '~> 0.0.0'
36
+ s.add_development_dependency 'rspec', '~> 2.6.0'
37
+ end
@@ -0,0 +1,63 @@
1
+ require "spec_helper"
2
+
3
+ module Rushiro
4
+ describe "Allow all deny some Access Control" do
5
+ let(:access_control) { DenyBasedControl.new(acl)}
6
+ describe "with no set permissions" do
7
+ let(:acl) { Hash.new }
8
+ it "should deny all" do
9
+ access_control.permitted?("page|view|posts").should be_true
10
+ access_control.serialize.should == {}
11
+ access_control.dirty.should be_false
12
+ end
13
+ end
14
+
15
+ describe "when adding a denies permission" do
16
+ let(:acl) { Hash.new }
17
+ it "should add the permission" do
18
+ access_control.add_permission("denies|individual|company|edit|acme-123")
19
+ access_control.dirty.should be_true
20
+ access_control.serialize.should_not == {}
21
+ access_control.permitted?("page|view|posts").should be_true
22
+ access_control.permitted?("company|view|acme-125").should be_true
23
+ access_control.permitted?("company|edit|acme-123").should be_false
24
+ end
25
+ end
26
+
27
+ describe "when removing a denies permission" do
28
+ let(:acl) { Hash.new }
29
+ it "should remove the permission" do
30
+ access_control.add_permission("denies|individual|company|edit|acme-123")
31
+ access_control.add_permission("denies|organization|page|edit")
32
+ access_control.permitted?("page|view|settings").should be_true
33
+ access_control.permitted?("page|edit|settings").should be_false
34
+ access_control.permitted?("company|edit|acme-123").should be_false
35
+ access_control.remove_permission("denies|organization|page|edit")
36
+ access_control.permitted?("page|view|settings").should be_true
37
+ access_control.permitted?("page|edit|settings").should be_true
38
+ access_control.permitted?("company|edit|acme-123").should be_false
39
+ end
40
+ end
41
+ end
42
+
43
+ describe "Deny all allow some Access Control" do
44
+ let(:access_control) { AllowBasedControl.new(acl)}
45
+ describe "with no set permissions" do
46
+ let(:acl) { Hash.new }
47
+ it "should deny all" do
48
+ access_control.permitted?("page|view|posts").should be_false
49
+ access_control.serialize.should == {}
50
+ access_control.dirty.should be_false
51
+ end
52
+ end
53
+
54
+ describe "when adding an allows permission" do
55
+ let(:acl) { Hash.new }
56
+ it "should add the permission" do
57
+ access_control.add_permission("allows|individual|page")
58
+ access_control.permitted?("page|view|posts").should be_true
59
+ access_control.permitted?("company|edit|acme-123").should be_false
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,11 @@
1
+ #$:.unshift File.expand_path("../../lib", __FILE__)
2
+
3
+ require 'ap'
4
+ #require 'cranky'
5
+ require 'rushiro'
6
+
7
+ def apr(what, header='')
8
+ ap "== #{header} =="
9
+ ap what
10
+ ap "="*(header.size + 6)
11
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rushiro
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Lee Henson
9
+ - Guy Boertje
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2011-11-03 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: awesome_print
17
+ requirement: &9402520 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 0.4.0
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *9402520
26
+ - !ruby/object:Gem::Dependency
27
+ name: fuubar
28
+ requirement: &9401920 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *9401920
37
+ - !ruby/object:Gem::Dependency
38
+ name: rspec
39
+ requirement: &9401440 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: 2.6.0
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *9401440
48
+ description: ''
49
+ email:
50
+ - lee.m.henson@gmail.com
51
+ - guyboertje@gmail.com
52
+ executables: []
53
+ extensions: []
54
+ extra_rdoc_files: []
55
+ files:
56
+ - Gemfile
57
+ - Rakefile
58
+ - lib/rushiro.rb
59
+ - lib/rushiro/access_control_hash.rb
60
+ - lib/rushiro/access_levels.rb
61
+ - lib/rushiro/allow_based_control.rb
62
+ - lib/rushiro/deny_based_control.rb
63
+ - lib/rushiro/permission.rb
64
+ - lib/rushiro/permissions.rb
65
+ - lib/rushiro/version.rb
66
+ - readme.md
67
+ - rushiro.gemspec
68
+ - spec/access_control_spec.rb
69
+ - spec/spec_helper.rb
70
+ homepage: http://github.com/guyboertje/rushiro
71
+ licenses: []
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 1.8.10
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: Explicit permissions inspired by Apache Shiro
94
+ test_files: []