credentials 2.1.0 → 2.2.0

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