authorule 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 94b612796ca264f8a74cc1a5279093c602cffe77
4
+ data.tar.gz: 8753373eca10d39f80ccbfa89469d5e6b7e30e34
5
+ SHA512:
6
+ metadata.gz: 90a3b01b1e02017f54ec9c3a931d0662a36b424e7f3c5c8c38f8277b2ceea8f8e646d5addad3cbf06261c095bce725302e71dcd1b204a718b57dda5a6c8858cf
7
+ data.tar.gz: 2ed129fc2f22109705614d3fd0f807eb79b88bb465503fd22c48751db6c1c266dc5c1291ba9fec4253b1b362c9be029a9e4285ee2e73beb0785eac275c5c85a2
data/.gitignore ADDED
@@ -0,0 +1,29 @@
1
+ # See http://help.github.com/ignore-files/ for more about ignoring files.
2
+ #
3
+ # If you find yourself ignoring temporary files generated by your text editor
4
+ # or operating system, you probably want to add a global ignore instead:
5
+ # git config --global core.excludesfile ~/.gitignore_global
6
+
7
+ # Ignore .DS_Store
8
+ .DS_Store
9
+
10
+ # Ignore the output files.
11
+ /pkg
12
+
13
+ # Ignore bundler and database config and precompiled assets
14
+ /.bundle
15
+ /.env
16
+
17
+ # Ignore all logfiles and tempfiles.
18
+ /tmp
19
+
20
+ # Ignore TextMate projects
21
+ *.tmproj
22
+ *.sublime-project
23
+ *.sublime-workspace
24
+
25
+ # Documentation files and other stuff
26
+ .yardoc
27
+ /doc
28
+ /nbproject
29
+ /coverage
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ flux
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.0.0-p247
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ script: bundle exec rake
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - 2.0.0
6
+
7
+ branches:
8
+ only:
9
+ - master
data/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ ## 1.0.0
2
+
3
+ * V1 Release
4
+
5
+ ## 0.0.2
6
+
7
+ * Update in documentation
8
+ * Bug fix in loading constants
9
+
10
+ ## 0.0.1
11
+
12
+ * Initial extraction from Flux framework
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in authorule.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,45 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ authorule (0.0.2)
5
+ activesupport (~> 3.2)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (3.2.14)
11
+ activesupport (= 3.2.14)
12
+ builder (~> 3.0.0)
13
+ activerecord (3.2.14)
14
+ activemodel (= 3.2.14)
15
+ activesupport (= 3.2.14)
16
+ arel (~> 3.0.2)
17
+ tzinfo (~> 0.3.29)
18
+ activesupport (3.2.14)
19
+ i18n (~> 0.6, >= 0.6.4)
20
+ multi_json (~> 1.0)
21
+ arel (3.0.2)
22
+ builder (3.0.4)
23
+ diff-lcs (1.2.4)
24
+ i18n (0.6.5)
25
+ multi_json (1.7.9)
26
+ rake (10.1.0)
27
+ rspec (2.14.1)
28
+ rspec-core (~> 2.14.0)
29
+ rspec-expectations (~> 2.14.0)
30
+ rspec-mocks (~> 2.14.0)
31
+ rspec-core (2.14.5)
32
+ rspec-expectations (2.14.3)
33
+ diff-lcs (>= 1.1.3, < 2.0)
34
+ rspec-mocks (2.14.3)
35
+ tzinfo (0.3.37)
36
+
37
+ PLATFORMS
38
+ ruby
39
+
40
+ DEPENDENCIES
41
+ activerecord (~> 3.2)
42
+ authorule!
43
+ bundler (~> 1.3)
44
+ rake
45
+ rspec (~> 2.14)
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Joost Lubach
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # Authorule
2
+
3
+ Rule based authorization library.
4
+
5
+ [<img src="https://secure.travis-ci.org/yoazt/authorule.png?branch=master" alt="Build Status" />](http://travis-ci.org/yoazt/authorule)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'authorule'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install authorule
20
+
21
+ ## Usage
22
+
23
+ ### Write rule model
24
+
25
+ Write a permission rule model. By default, `Authorule` expects this to be called `PermissionRule`. You may add any associations in the class, but this is not required by this gem.
26
+
27
+ class PermissionRule < ActiveRecord::Base
28
+ include Authorule::Rule
29
+
30
+ belongs_to :user
31
+ end
32
+
33
+ Use the following migration for this class (TODO: write a generator):
34
+
35
+ class CreatePermissionRules < ActiveRecord::Migration
36
+ def change
37
+ create_table :permission_rules do |t|
38
+ t.boolean :allow, :default => true
39
+ t.string :kind, :limit => 20
40
+ t.string :name, :limit => 80
41
+ t.string :action, :limit => 20
42
+
43
+ # --> Add any other columns here.
44
+ end
45
+ end
46
+ end
47
+
48
+ ### Write permission holder
49
+
50
+ Write a permission holder model. This is typically a `User` object. Include `Authorule::PermissionHolder` into this class, and call `is_permission_holder!`.
51
+
52
+ class User < ActiveRecord::Base
53
+ include Authorule::PermissionHolder
54
+
55
+ is_permission_holder!
56
+ end
57
+
58
+ This creates an association and a rule base accessor. By default, it is assumed that the rule class is called `PermissionRule`.
59
+
60
+ ### Write a custom permission class
61
+
62
+ Write a permission class. Each permission class should at a minimum:
63
+
64
+ 1. Register itself under a name.
65
+ 2. Provide a way to resolve any argument into a permission target.
66
+ 3. Provide a way to list all permission targets.
67
+
68
+ A permission target is the object that you wish to secure using the permission. The following example is a permission that secures access to any ActiveRecord object. The target is the class (e.g. 'Allow user X to access Active Record class Y.'), but the permission can also resolve model instances.
69
+
70
+ class ResourcePermission < Authorule::Permission
71
+
72
+ # Register under name :resource.
73
+ register :resource
74
+
75
+ # Resolution.
76
+ resolve do |arg|
77
+ if arg.is_a?(ActiveRecord::Base)
78
+ arg.class
79
+ elsif arg.is_a?(Class) && arg < ActiveRecord::Base
80
+ arg
81
+ end
82
+ end
83
+
84
+ list do
85
+ classes = []
86
+ Dir[ Rails.root + 'app/models' + '*.rb' ].each do |file|
87
+ klass = File.basename(file, '.rb').camelize.safe_constantize
88
+ classes << klass if klass
89
+ end
90
+ classes
91
+ end
92
+
93
+ end
94
+
95
+ ### Checking permissions
96
+
97
+ You can now give any user a set of rules, e.g.:
98
+
99
+ 1. Allow access to everything
100
+ 2. Deny access to ActiveRecord class 'Account'
101
+
102
+ This allows the user to access all other ActiveRecord classes (and their objects).
103
+
104
+ To check a permission, you can call:
105
+
106
+ User.may_access?(Account)
107
+
108
+ or
109
+
110
+ User.may_access?(Account.new)
111
+
112
+ (because it resolves into a class), or the equivalent
113
+
114
+ permission = Authorule.resolve(Account.new)
115
+ User.has_permission?(permission)
116
+
117
+ ## Contributing
118
+
119
+ 1. Fork it
120
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
121
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
122
+ 4. Push to the branch (`git push origin my-new-feature`)
123
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
data/authorule.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'authorule/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "authorule"
8
+ spec.version = Authorule::VERSION
9
+ spec.authors = ["Joost Lubach"]
10
+ spec.email = ["joost@yoazt.com"]
11
+ spec.description = %q[Rule-based programmable permission system.]
12
+ spec.summary = %q[Rule-based programmable permission system.]
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^spec/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activesupport", "~> 3.2"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "activerecord", "~> 3.2"
26
+ spec.add_development_dependency "rspec", "~> 2.14"
27
+ end
@@ -0,0 +1,123 @@
1
+ module Authorule
2
+
3
+ # A permission. This is an object that can be used to check if someone has access to a certain permissable.
4
+ #
5
+ # Note: do not confuse a permission with a {PermissionRule} or {PermissionRuleBase}. This class doesn't indicate
6
+ # that a user has been granted a permission. It simply encapsulates a permission query.
7
+ #
8
+ # This class should also not be confused with a {CustomPermission}, which is an application-defined custom
9
+ # permission.
10
+ #
11
+ # == Usage
12
+ #
13
+ # permission = Permission.resolve(Campaign, :destroy)
14
+ # @user.has_permission?(permission) # Granted that @user < UI::PermissionHolder
15
+ #
16
+ # Or even simpler:
17
+ #
18
+ # @user.may?(:destroy, @campaign)
19
+ #
20
+ # == Object resolution
21
+ #
22
+ # Any object can be converted into a permission, if a suitable {Schema} can be found. For example, any UI resource
23
+ # can be resolved into a resource permission, but also any resource model class or even resource symbol. This
24
+ # allows for the following equivalent calls:
25
+ #
26
+ # @user.may?(:destroy, UI.application.resources[:campaign])
27
+ # @user.may?(:destroy, @campaign)
28
+ # @user.may?(:destroy, Campaign)
29
+ # @user.may?(:destroy, :campaign)
30
+ #
31
+ # The UI library defines a few schemas, for example one for resource permissions, and one for UI space permissions.
32
+ # There is also a custom permission schema - allowing the application designer to define additional permissions.
33
+ # These can be referred to throughout the UI library.
34
+ #
35
+ # @see PermissionHolder
36
+ # @see RuleBase
37
+ # @see Rule
38
+ class Permission
39
+
40
+ ######
41
+ # Initialization
42
+
43
+ # Initializes a new permission.
44
+ #
45
+ # @param object
46
+ # The object of the permission.
47
+ # @param [Symbol|nil] action
48
+ # The action the user wishes to perform.
49
+ def initialize(object, action = nil)
50
+ @object = object
51
+ @action = action.try(:to_sym)
52
+ end
53
+
54
+ ######
55
+ # Attributes
56
+
57
+ # @!attribute [r] object
58
+ # @return [Symbol] The object of the permission.
59
+ attr_reader :object
60
+
61
+ # @!attribute [r] action
62
+ # @return [Symbol|nil] The action the user wishes to perform.
63
+ attr_reader :action
64
+
65
+ # @!attribute [r] kind
66
+ # @return [Symbol] The kind of permission. This is delegated to the current class.
67
+ def kind
68
+ self.class.kind
69
+ end
70
+
71
+ # @!attribute [r] name
72
+ # @return [String] The name of the permission. This is delegated to {#object}.
73
+ def name
74
+ object.name
75
+ end
76
+
77
+ # @!attribute [r] available_actions
78
+ # @return [Array] The available actions for the permission.
79
+ def available_actions
80
+ []
81
+ end
82
+
83
+ ######
84
+ # Dependencies
85
+
86
+ # Retrieves an array of permissions consisting of dependencies and the permission itself.
87
+ def with_dependencies
88
+ dependencies + [ self ]
89
+ end
90
+
91
+ # Resolves dependencies for this permission. To be implemented by subclasses.
92
+ def dependencies
93
+ []
94
+ end
95
+
96
+ ######
97
+ # Registration & metadata
98
+
99
+ class << self
100
+
101
+ attr_reader :kind, :resolve_block, :list_block
102
+
103
+ # Registers a permission class under a specific kind.
104
+ def register(kind)
105
+ Authorule.register kind, self
106
+ @kind = kind
107
+ end
108
+
109
+ # Defines a block that resolves any argument into a suitable permission target.
110
+ def resolve(&block)
111
+ @resolve_block = block
112
+ end
113
+
114
+ # Defines a block that lists all suitable permission targets in the application.
115
+ def list(&block)
116
+ @list_block = block
117
+ end
118
+
119
+ end
120
+
121
+ end
122
+
123
+ end
@@ -0,0 +1,47 @@
1
+ module Authorule
2
+
3
+ # Provides methods 'may?', 'may_access?' and their negative counterparts. You must make sure
4
+ # to implement method 'has_permission?' in your class.
5
+ module PermissionAccessors
6
+
7
+ # Determines whether a holder in this group may perform the specified action on the specified target.
8
+ #
9
+ # @param [#to_s] action
10
+ # The action to perform. The available actions differ per permissions. The full list can be found
11
+ # in {UI::Permission}.
12
+ # @param target
13
+ # The target the holder wishes to operate on. This target may be any object and is passed to the UI
14
+ # permission checker as is, which will convert it into a permission path.
15
+ def may?(action, target)
16
+ permission = Authorule.resolve(target, action)
17
+ unless permission.available_actions.try(:include?, action)
18
+ raise ArgumentError, "action :#{action} not available for permission of kind :#{permission.class.kind}"
19
+ end
20
+
21
+ has_permission? permission
22
+ end
23
+
24
+ # Checks a permission without querying a specific action.
25
+ # @see #may?
26
+ def may_access?(target)
27
+ permission = Authorule.resolve(target)
28
+ has_permission? permission
29
+ end
30
+
31
+ # Determines whether a holder may not perform the specified action on the specified target.
32
+ # @see #may?
33
+ def may_not?(action, target)
34
+ !may?(action, target)
35
+ end
36
+
37
+ # Determines whether a holder may not access the specified target.
38
+ # @see #may_not?
39
+ # @see #may?
40
+ def may_not_access?(target)
41
+ !may_access?(target)
42
+ end
43
+
44
+
45
+
46
+ end
47
+ end
@@ -0,0 +1,55 @@
1
+ module Authorule
2
+
3
+ # Makes any ActiveModel/ActiveRecord-like class a UI permission holder.
4
+ #
5
+ # == Usage
6
+ #
7
+ # class User
8
+ # include Authorule::PermissionHolder
9
+ # is_permission_holder!
10
+ # end
11
+ #
12
+ # * A (has many) +permission_rules+ association is added to the model (though the
13
+ # name may be changed in the {.is_permission_holder!} method).
14
+ # * A {#may?} and {#may_not?} method is added.
15
+ module PermissionHolder
16
+ extend ActiveSupport::Concern
17
+
18
+ include PermissionAccessors
19
+
20
+ module ClassMethods
21
+
22
+ # Marks this class as a permission holder with the given options.
23
+ #
24
+ # @option options [#to_sym] association_name (:permission_rules)
25
+ # The name of the permission rules association.
26
+ def is_permission_holder!(options = {})
27
+ association_name = options[:association_name] || :permission_rules
28
+
29
+ class_eval <<-RUBY, __FILE__, __LINE__+1
30
+ has_many :#{association_name}
31
+
32
+ def permission_rule_base(reload = false)
33
+ @permission_rule_base = nil if reload
34
+ @permission_rule_base ||= RuleBase.new(#{association_name}(true))
35
+ end
36
+ RUBY
37
+ end
38
+
39
+ end
40
+
41
+ ######
42
+ # has_permission?
43
+
44
+ # Determines whether this holder has the given permission by running it through his rule base.
45
+ def has_permission?(permission)
46
+ unless respond_to?(:permission_rule_base)
47
+ raise "class not set up as permission holder, call is_permission_holder! first"
48
+ end
49
+
50
+ permission_rule_base.run permission
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,21 @@
1
+ require File.expand_path('..', __FILE__)
2
+
3
+ module Authorule
4
+
5
+ class Railtie < Rails::Railtie
6
+
7
+ generators do
8
+ Dir[ File.expand_path('../generators/*/*_generator.rb', __FILE__) ].each do |path|
9
+ require path
10
+ end
11
+ end
12
+
13
+ rake_tasks do
14
+ Dir[ File.expand_path('../tasks/**/*.rake', __FILE__) ].each do |path|
15
+ load path
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,111 @@
1
+ module Authorule
2
+
3
+ # A permission rule. Each rule allows or denies the permission holder one permission.
4
+ #
5
+ # == Usage
6
+ #
7
+ # Create a model class, and include this mixin into it, e.g.
8
+ #
9
+ # class PermissionRule < ActiveRecord::Base
10
+ # include Authorule::Rule
11
+ #
12
+ # belongs_to :user
13
+ # end
14
+ #
15
+ # @see RuleBase
16
+ module Rule
17
+ extend ActiveSupport::Concern
18
+
19
+ ######
20
+ # Attributes & validations
21
+
22
+ included do
23
+ validates_inclusion_of :allow, :in => [ true, false ]
24
+ validates_presence_of :kind, :name
25
+
26
+ validates_length_of :kind, :maximum => 20
27
+ validates_length_of :name, :maximum => 80
28
+ validates_length_of :action, :maximum => 20, :allow_blank => true
29
+
30
+ # Make sure to coerce a blank value for action into an absolute nil.
31
+ before_validation { self.action = nil if self.action.blank? }
32
+ end
33
+
34
+ ######
35
+ # Rule creation accessors
36
+
37
+ # Builds an allow rule for the given kind and name.
38
+ def self.allow(kind, name, attributes = {})
39
+ new attributes.merge(:kind => kind, :name => name, :allow => true)
40
+ end
41
+
42
+ # Creates an allow rule for the given kind and name.
43
+ def self.allow!(kind, name, attributes = {})
44
+ allow(kind, name, attributes).save
45
+ end
46
+
47
+ # Builds a deny rule for the given kind and name.
48
+ def self.deny(kind, name, attributes = {})
49
+ new attributes.merge(:kind => kind, :name => name, :allow => false)
50
+ end
51
+
52
+ # Creates a deny rule for the given kind and name.
53
+ def self.deny!(kind, name, attributes = {})
54
+ deny(kind, name, attributes).save
55
+ end
56
+
57
+ # Builds an 'allow all' rule.
58
+ #
59
+ # == Examples
60
+ #
61
+ # Rule.allow_all # => kind 'all', name 'all'
62
+ # Rule.allow_all(:resource) # => kind 'resource', name 'all'
63
+ def self.allow_all(kind = :all, attributes = {})
64
+ new attributes.merge(:kind => kind, :name => 'all', :allow => true)
65
+ end
66
+
67
+ # Creates an 'allow all' rule.
68
+ # @see .allow_all
69
+ def self.allow_all!(kind = :all, attributes = {})
70
+ allow_all(kind, attributes).save
71
+ end
72
+
73
+ # Creates a 'deny all' rule.
74
+ #
75
+ # == Examples
76
+ #
77
+ # Rule.deny_all # => kind 'all', name 'all'
78
+ # Rule.deny_all(:resource) # => kind 'resource', name 'all'
79
+ def self.deny_all(kind = :all, attributes = {})
80
+ new attributes.merge(:kind => kind, :name => 'all', :allow => false)
81
+ end
82
+
83
+ # Creates an 'deny all' rule.
84
+ # @see .deny_all
85
+ def self.deny_all!(kind = :all, attributes = {})
86
+ allow_all(kind, attributes).save
87
+ end
88
+
89
+ ######
90
+ # Rule key
91
+
92
+ # @!attribute [r] key
93
+ # @return [String] A unique key identifying this rule.
94
+ def key
95
+ if kind == 'all'
96
+ 'all'
97
+ else
98
+ [ kind, name, action ].compact.join(':')
99
+ end
100
+ end
101
+
102
+ ######
103
+ # Misc
104
+
105
+ def to_display
106
+ key
107
+ end
108
+
109
+ end
110
+
111
+ end