rushiro 1.0.0

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