authorule 1.0.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
+ 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