credentials 2.1.0 → 2.2.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/HISTORY CHANGED
@@ -1,3 +1,7 @@
1
+ === 2.2.0 / 2009-11-24
2
+
3
+ * Added support for ActionController
4
+
1
5
  === 2.1.0 / 2009-11-12
2
6
 
3
7
  * Added support for :self
data/README.rdoc CHANGED
@@ -82,7 +82,6 @@ and turned into a nice pretty error page.
82
82
 
83
83
  == To do
84
84
 
85
- * Proper documentation of the API.
86
85
  * Better support for groups would make integration with RBA systems easier.
87
86
 
88
87
  == License
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.1.0
1
+ 2.2.0
data/credentials.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{credentials}
8
- s.version = "2.1.0"
8
+ s.version = "2.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Matt Powell"]
12
- s.date = %q{2009-11-12}
12
+ s.date = %q{2009-11-24}
13
13
  s.description = %q{A generic actor/resource permission framework based on rules, not objects.}
14
14
  s.email = %q{fauxparse@gmail.com.com}
15
15
  s.extra_rdoc_files = [
@@ -29,10 +29,14 @@ Gem::Specification.new do |s|
29
29
  "lib/credentials.rb",
30
30
  "lib/credentials/allow_rule.rb",
31
31
  "lib/credentials/deny_rule.rb",
32
- "lib/credentials/object_extensions.rb",
32
+ "lib/credentials/errors.rb",
33
+ "lib/credentials/extensions/action_controller.rb",
34
+ "lib/credentials/extensions/configuration.rb",
35
+ "lib/credentials/extensions/object.rb",
33
36
  "lib/credentials/rule.rb",
34
37
  "lib/credentials/rulebook.rb",
35
38
  "spec/.gitignore",
39
+ "spec/controllers/test_controller_spec.rb",
36
40
  "spec/credentials_spec.rb",
37
41
  "spec/domain.rb",
38
42
  "spec/rule_spec.rb",
@@ -50,7 +54,8 @@ Gem::Specification.new do |s|
50
54
  s.rubygems_version = %q{1.3.5}
51
55
  s.summary = %q{A generic actor/resource permission framework based on rules, not objects.}
52
56
  s.test_files = [
53
- "spec/credentials_spec.rb",
57
+ "spec/controllers/test_controller_spec.rb",
58
+ "spec/credentials_spec.rb",
54
59
  "spec/domain.rb",
55
60
  "spec/rule_spec.rb",
56
61
  "spec/rulebook_spec.rb",
data/lib/credentials.rb CHANGED
@@ -1,4 +1,8 @@
1
1
  require "credentials/rulebook"
2
- require "credentials/object_extensions"
2
+ require "credentials/extensions/object"
3
3
 
4
- Object.send :include, Credentials::ObjectExtensions
4
+ Object.send :include, Credentials::Extensions::Object
5
+
6
+ if defined?(ActionController)
7
+ ActionController::Base.send :include, Credentials::Extensions::ActionController
8
+ end
@@ -0,0 +1,15 @@
1
+ module Credentials
2
+ module Errors
3
+ class NotLoggedInError < StandardError
4
+
5
+ end
6
+
7
+ class AccessDeniedError < StandardError
8
+ attr_reader :args
9
+
10
+ def initialize(*args)
11
+ @args = args
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,97 @@
1
+ module Credentials
2
+ module Extensions
3
+ module ActionController
4
+ module ClassMethods
5
+ # Specify a requirement for the currently logged-in user
6
+ # to be able to access particular actions.
7
+ #
8
+ # The current user is determined by calling the method named in
9
+ # +self.class.current_user_method+ (default is +current_user+).
10
+ # If there is a rule set against the current action and no user
11
+ # is logged in, then a Credentials::Errors::NotLoggedInError is
12
+ # raised.
13
+ #
14
+ # Otherwise, the rules are treated like 'before' filters, with
15
+ # the result being either a pass (action is executed as normal)
16
+ # or a failure (Credentials::Errors::AccessDeniedError is raised).
17
+ # (Note that evaluation stops at the first failure.)
18
+ #
19
+ # Just like ActionController's built-in filters, you can use
20
+ # +only+ and +unless+ to restrict the scope of your rules.
21
+ #
22
+ # == Credential tests
23
+ #
24
+ # For the most part, these are carried out as you'd expect:
25
+ # requires_permission_to :create, Post
26
+ # # checks current_user.can? :create, Post
27
+ #
28
+ # However, the magic part is that any symbol arguments are
29
+ # evaluated against the current controller instance, if
30
+ # matching methods can be found, allowing you to do this:
31
+ # class PostsController
32
+ # requires_permission_to :edit, :current_post,
33
+ # :only => %w(edit update destroy)
34
+ #
35
+ # def edit
36
+ # # ...
37
+ # end
38
+ #
39
+ # protected
40
+ # def current_post
41
+ # @current_post ||= Post.find params[:id]
42
+ # end
43
+ # end
44
+ def requires_permission_to(*args)
45
+ options = (args.last.is_a?(Hash) ? args.pop : {}).with_indifferent_access
46
+ %w(only except).each do |key|
47
+ options[key] = Array(options[key]).map(&:to_sym) if options[key]
48
+ end
49
+ self.required_credentials = self.required_credentials + [ [ options, args ] ]
50
+ end
51
+
52
+ def required_credentials #:nodoc:
53
+ read_inheritable_attribute(:required_credentials) || []
54
+ end
55
+
56
+ # Sets the method for determining the current user in a
57
+ # controller instance.
58
+ # (Default: +:current_user+)
59
+ def current_user_method(value = nil)
60
+ rw_config(:current_user_method, value, :current_user)
61
+ end
62
+ alias_method :current_user_method=, :current_user_method
63
+ end
64
+
65
+ protected
66
+ # Acts as a +before_filter+ to check credentials before an action
67
+ # is executed.
68
+ #
69
+ # See Credentials::Extensions::ActionController::ClassMethods#requires_permission_to
70
+ # for more details.
71
+ def check_credentials
72
+ current_user = send self.class.current_user_method
73
+ current_action = action_name.to_sym
74
+
75
+ self.class.required_credentials.each do |options, args|
76
+ next if options[:only] && !options[:only].include?(current_action)
77
+ next if options[:except] && options[:except].include?(current_action)
78
+
79
+ raise Credentials::Errors::NotLoggedInError unless current_user
80
+ evaluated = args.map { |arg| (arg.is_a?(Symbol) && respond_to?(arg)) ? send(arg) : arg }
81
+
82
+ unless current_user.can?(*evaluated)
83
+ raise Credentials::Errors::AccessDeniedError
84
+ end
85
+ end
86
+ end
87
+
88
+ def self.included(receiver) #:nodoc:
89
+ receiver.extend Credentials::Extensions::Configuration
90
+ receiver.extend ClassMethods
91
+
92
+ receiver.send :class_inheritable_writer, :required_credentials
93
+ receiver.before_filter :check_credentials
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,13 @@
1
+ module Credentials #:nodoc:
2
+ module Extensions #:nodoc:
3
+ module Configuration #:nodoc:
4
+ def rw_config(key, value, default_value = nil, read_value = nil)
5
+ if value == read_value
6
+ inheritable_attributes.include?(key) ? read_inheritable_attribute(key) : default_value
7
+ else
8
+ write_inheritable_attribute(key, value)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,89 @@
1
+ module Credentials
2
+ module Extensions #:nodoc:
3
+ module Object
4
+ module ClassMethods
5
+ # The main method for specifying and retrieving the permissions of
6
+ # a member of this class.
7
+ #
8
+ # When called with a block, this method yields a Credentials::Rulebook
9
+ # object, allowing you to specify the class's credentials
10
+ # in a declarative fashion. For example:
11
+ #
12
+ # class User
13
+ # credentials do |user|
14
+ # user.can :edit, User, :if => :administrator?
15
+ # user.can :edit, :self
16
+ # end
17
+ # end
18
+ #
19
+ # You can also specify options in this way:
20
+ #
21
+ # class User
22
+ # credentials(:default => :allow) do |user|
23
+ # user.cannot :eat, "ice cream", :if => :lactose_intolerant?
24
+ # end
25
+ # end
26
+ #
27
+ # The following options are supported:
28
+ #
29
+ # [+:default+] Whether to +:allow+ or +:deny+ permissions that aren't
30
+ # specified explicitly. The default default (!) is +:deny+.
31
+ #
32
+ # When called without a block, +credentials+ just returns the class's
33
+ # Credentials::Rulebook, creating it if necessary.
34
+ def credentials(options = nil)
35
+ @credentials ||= Credentials::Rulebook.new(self)
36
+ if block_given?
37
+ @credentials.options.merge!(options) unless options.nil?
38
+ yield @credentials
39
+ else
40
+ raise ArgumentError, "you can only set options with a block" unless options.nil?
41
+ end
42
+ @credentials
43
+ end
44
+
45
+ def inherited_with_credentials(child_class) #:nodoc:
46
+ inherited_without_credentials(child_class) if child_class.respond_to? :inherited_without_credentials
47
+ child_class.instance_variable_set("@credentials", Rulebook.for(child_class))
48
+ end
49
+ end
50
+
51
+ # Returns true if the receiver has access to the specified resource or action.
52
+ def can?(*args)
53
+ self.class.credentials.allow? self, *args
54
+ end
55
+ alias_method :able_to?, :can?
56
+
57
+ # Allows you to use magic methods to test permissions.
58
+ # For example:
59
+ #
60
+ # class User
61
+ # credentials do |user|
62
+ # user.can :edit, :self
63
+ # end
64
+ # end
65
+ #
66
+ # user = User.new
67
+ # user.can_edit? user #=> true
68
+ def method_missing_with_credentials(sym, *args)
69
+ if sym.to_s =~ /\Acan_(.*)\?\z/
70
+ can? $1.to_sym, *args
71
+ else
72
+ method_missing_without_credentials sym, *args
73
+ end
74
+ end
75
+
76
+ def self.included(receiver) #:nodoc:
77
+ receiver.extend ClassMethods
78
+
79
+ receiver.send :alias_method, :method_missing_without_credentials, :method_missing
80
+ receiver.send :alias_method, :method_missing, :method_missing_with_credentials
81
+
82
+ class << receiver
83
+ alias_method :inherited_without_credentials, :inherited if respond_to? :inherited
84
+ alias_method :inherited, :inherited_with_credentials
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,99 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ class TestController < ActionController::Base
4
+ self.current_user_method = :logged_in_user
5
+ requires_permission_to :view, :stuff, :except => [ :public ]
6
+ requires_permission_to :break, :stuff, :only => [ :dangerous ]
7
+
8
+ def index; end
9
+ def public; end
10
+ def dangerous; end
11
+
12
+ def rescue_action(e)
13
+ raise e
14
+ end
15
+ end
16
+
17
+ class TestUser
18
+ credentials do |user|
19
+ user.can :view, :stuff
20
+ user.can :break, :stuff, :if => :special?
21
+ end
22
+ end
23
+
24
+ describe TestController do
25
+ it "should know how to specify access credentials" do
26
+ controller.class.should respond_to :requires_permission_to
27
+ end
28
+
29
+ it "should use the right method to look up the current user" do
30
+ controller.class.current_user_method.should == :logged_in_user
31
+ end
32
+
33
+ it "should check credentials on each request" do
34
+ controller.should_receive(:check_credentials)
35
+ get :index
36
+ end
37
+
38
+ describe "when logged in" do
39
+ before :each do
40
+ @user = TestUser.new
41
+ controller.should_receive(:logged_in_user).and_return(@user)
42
+ end
43
+
44
+ it "should display stuff" do
45
+ lambda {
46
+ get :index
47
+ response.should be_success
48
+ }.should_not raise_error(Credentials::Errors::NotLoggedInError)
49
+ end
50
+
51
+ describe "as someone with permission to break stuff" do
52
+ before(:each) do
53
+ @user.stub!(:special?).and_return(true)
54
+ @user.should be_able_to(:view, :stuff)
55
+ @user.should be_able_to(:break, :stuff)
56
+ end
57
+
58
+ it "should have access to dangerous actions" do
59
+ lambda {
60
+ get :dangerous
61
+ response.should be_success
62
+ }.should_not raise_error(Credentials::Errors::AccessDeniedError)
63
+ end
64
+ end
65
+
66
+ describe "as someone without permission to break stuff" do
67
+ before(:each) do
68
+ @user.stub!(:special?).and_return(false)
69
+ @user.should_not be_able_to(:break, :stuff)
70
+ end
71
+
72
+ it "should not have access to dangerous actions" do
73
+ lambda {
74
+ get :dangerous
75
+ response.should_not be_success
76
+ }.should raise_error(Credentials::Errors::AccessDeniedError)
77
+ end
78
+ end
79
+ end
80
+
81
+ describe "when not logged in" do
82
+ before :each do
83
+ controller.should_receive(:logged_in_user).and_return(nil)
84
+ end
85
+
86
+ it "should not have access to stuff" do
87
+ lambda {
88
+ get :index
89
+ }.should raise_error(Credentials::Errors::NotLoggedInError)
90
+ end
91
+
92
+ it "should have access to public stuff" do
93
+ lambda {
94
+ get :public
95
+ response.should be_success
96
+ }.should_not raise_error(Credentials::Errors::AccessDeniedError)
97
+ end
98
+ end
99
+ end
data/spec/domain.rb CHANGED
@@ -1,8 +1,16 @@
1
- class Animal < Struct.new(:species, :hungry, :fast)
1
+ class Animal
2
2
  credentials do |animal|
3
3
  animal.can :clean, :self
4
4
  end
5
5
 
6
+ attr_accessor :species, :hungry, :fast
7
+
8
+ def initialize(species = nil, hungry = false, fast = false)
9
+ @species = species
10
+ @hungry = hungry
11
+ @fast = fast
12
+ end
13
+
6
14
  def edible?
7
15
  false
8
16
  end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,11 @@
1
- $: << File.dirname(__FILE__) + "/../lib"
2
- require "credentials"
3
- require "spec"
4
- require "date"
1
+ begin
2
+ require File.dirname(__FILE__) + '/../../../../spec/spec_helper'
3
+ rescue LoadError
4
+ puts "NOTE: install rspec in your base app to test Rails integration"
5
+ $: << File.dirname(__FILE__) + "/../lib"
6
+ require "credentials"
7
+ require "spec"
8
+ require "date"
9
+ end
5
10
 
6
11
  require File.join(File.dirname(__FILE__), "domain.rb")
data/tasks/spec.rake CHANGED
@@ -38,7 +38,7 @@ namespace :spec do
38
38
  t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
39
39
  t.spec_files = FileList['spec/**/*_spec.rb']
40
40
  t.rcov = true
41
- t.rcov_opts = ['--text-report --exclude "spec/*"']
41
+ t.rcov_opts = ['--text-report --rails --exclude "spec/*,application_controller.rb,application_helper.rb"']
42
42
  end
43
43
  end
44
44
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: credentials
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Powell
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-12 00:00:00 +13:00
12
+ date: 2009-11-24 00:00:00 +13:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -35,10 +35,14 @@ files:
35
35
  - lib/credentials.rb
36
36
  - lib/credentials/allow_rule.rb
37
37
  - lib/credentials/deny_rule.rb
38
- - lib/credentials/object_extensions.rb
38
+ - lib/credentials/errors.rb
39
+ - lib/credentials/extensions/action_controller.rb
40
+ - lib/credentials/extensions/configuration.rb
41
+ - lib/credentials/extensions/object.rb
39
42
  - lib/credentials/rule.rb
40
43
  - lib/credentials/rulebook.rb
41
44
  - spec/.gitignore
45
+ - spec/controllers/test_controller_spec.rb
42
46
  - spec/credentials_spec.rb
43
47
  - spec/domain.rb
44
48
  - spec/rule_spec.rb
@@ -78,6 +82,7 @@ signing_key:
78
82
  specification_version: 3
79
83
  summary: A generic actor/resource permission framework based on rules, not objects.
80
84
  test_files:
85
+ - spec/controllers/test_controller_spec.rb
81
86
  - spec/credentials_spec.rb
82
87
  - spec/domain.rb
83
88
  - spec/rule_spec.rb
@@ -1,90 +0,0 @@
1
- module Credentials
2
- module ObjectExtensions #:nodoc:
3
- module ClassMethods
4
- # The main method for specifying and retrieving the permissions of
5
- # a member of this class.
6
- #
7
- # When called with a block, this method yields a Credentials::Rulebook
8
- # object, allowing you to specify the class's credentials
9
- # in a declarative fashion. For example:
10
- #
11
- # class User
12
- # credentials do |user|
13
- # user.can :edit, User, :if => :administrator?
14
- # user.can :edit, :self
15
- # end
16
- # end
17
- #
18
- # You can also specify options in this way:
19
- #
20
- # class User
21
- # credentials(:default => :allow) do |user|
22
- # user.cannot :eat, "ice cream", :if => :lactose_intolerant?
23
- # end
24
- # end
25
- #
26
- # The following options are supported:
27
- #
28
- # [+:default+] Whether to +:allow+ or +:deny+ permissions that aren't
29
- # specified explicitly. The default default (!) is +:deny+.
30
- #
31
- # When called without a block, +credentials+ just returns the class's
32
- # Credentials::Rulebook, creating it if necessary.
33
- def credentials(options = nil)
34
- @credentials ||= Credentials::Rulebook.new(self)
35
- if block_given?
36
- @credentials.options.merge!(options) unless options.nil?
37
- yield @credentials
38
- else
39
- raise ArgumentError, "you can only set options with a block" unless options.nil?
40
- end
41
- @credentials
42
- end
43
-
44
- def inherited_with_credentials(child_class) #:nodoc:
45
- inherited_without_credentials(child_class) if child_class.respond_to? :inherited_without_credentials
46
- child_class.instance_variable_set("@credentials", Rulebook.for(child_class))
47
- end
48
- end
49
-
50
- module InstanceMethods
51
- # Returns true if the receiver has access to the specified resource or action.
52
- def can?(*args)
53
- self.class.credentials.allow? self, *args
54
- end
55
- alias_method :able_to?, :can?
56
-
57
- # Allows you to use magic methods to test permissions.
58
- # For example:
59
- #
60
- # class User
61
- # credentials do |user|
62
- # user.can :edit, :self
63
- # end
64
- # end
65
- #
66
- # user = User.new
67
- # user.can_edit? user #=> true
68
- def method_missing_with_credentials(sym, *args)
69
- if sym.to_s =~ /\Acan_(.*)\?\z/
70
- can? $1.to_sym, *args
71
- else
72
- method_missing_without_credentials sym, *args
73
- end
74
- end
75
- end
76
-
77
- def self.included(receiver) #:nodoc
78
- receiver.extend ClassMethods
79
- receiver.send :include, InstanceMethods
80
-
81
- receiver.send :alias_method, :method_missing_without_credentials, :method_missing
82
- receiver.send :alias_method, :method_missing, :method_missing_with_credentials
83
-
84
- class << receiver
85
- alias_method :inherited_without_credentials, :inherited if respond_to? :inherited
86
- alias_method :inherited, :inherited_with_credentials
87
- end
88
- end
89
- end
90
- end