permit 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +5 -0
  2. data/.yardopts +3 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.mkd +238 -0
  5. data/Rakefile +69 -0
  6. data/VERSION.yml +5 -0
  7. data/generators/permit/USAGE +40 -0
  8. data/generators/permit/permit_generator.rb +25 -0
  9. data/generators/permit/templates/authorization.rb +2 -0
  10. data/generators/permit/templates/initializer.rb +37 -0
  11. data/generators/permit/templates/migration.rb +28 -0
  12. data/generators/permit/templates/role.rb +2 -0
  13. data/init.rb +1 -0
  14. data/install.rb +1 -0
  15. data/lib/models/association.rb +89 -0
  16. data/lib/models/authorizable.rb +31 -0
  17. data/lib/models/authorization.rb +54 -0
  18. data/lib/models/person.rb +148 -0
  19. data/lib/models/role.rb +59 -0
  20. data/lib/permit/controller.rb +132 -0
  21. data/lib/permit/permit_rule.rb +198 -0
  22. data/lib/permit/permit_rules.rb +141 -0
  23. data/lib/permit/support.rb +67 -0
  24. data/lib/permit.rb +134 -0
  25. data/permit.gemspec +91 -0
  26. data/rails/init.rb +7 -0
  27. data/spec/models/alternate_models_spec.rb +54 -0
  28. data/spec/models/authorizable_spec.rb +78 -0
  29. data/spec/models/authorization_spec.rb +77 -0
  30. data/spec/models/person_spec.rb +278 -0
  31. data/spec/models/role_spec.rb +121 -0
  32. data/spec/permit/controller_spec.rb +308 -0
  33. data/spec/permit/permit_rule_spec.rb +452 -0
  34. data/spec/permit/permit_rules_spec.rb +273 -0
  35. data/spec/permit_spec.rb +58 -0
  36. data/spec/spec_helper.rb +73 -0
  37. data/spec/support/helpers.rb +13 -0
  38. data/spec/support/models.rb +38 -0
  39. data/spec/support/permits_controller.rb +7 -0
  40. data/tasks/permit_tasks.rake +4 -0
  41. data/uninstall.rb +1 -0
  42. metadata +107 -0
@@ -0,0 +1,67 @@
1
+ module Permit
2
+ module Support
3
+ module ClassMethods
4
+ def class_symbol
5
+ class_name.underscore.to_sym
6
+ end
7
+
8
+ def plural_class_symbol
9
+ class_name.pluralize.underscore.to_sym
10
+ end
11
+
12
+ private
13
+ def permit_authorized_model(custom_options = {})
14
+ options = {:class_name => Permit::Config.authorization_class.name, :extend => Permit::Models::AssociationExtensions}.merge(custom_options)
15
+ has_many Permit::Config.authorization_class.plural_class_symbol, options
16
+
17
+ class_eval <<-END
18
+ protected
19
+ def permit_authorizations_proxy
20
+ #{Permit::Config.authorization_class.plural_class_symbol}
21
+ end
22
+ END
23
+ end
24
+ end
25
+
26
+ # Converts an object to an array of that object if it is not already one.
27
+ #
28
+ # @param [Object] o the object to be made into an Array
29
+ # @return [Array] the object as an array.
30
+ def permit_arrayify(o)
31
+ Array===o ? o : [o]
32
+ end
33
+
34
+ protected
35
+ def authorization_conditions(role, resource, person = nil)
36
+ conditions = {}
37
+ conditions[Permit::Config.person_class.name.foreign_key] = person.id if person
38
+ conditions.merge! role_condition(role)
39
+ conditions.merge! resource_conditions(resource)
40
+ end
41
+
42
+ def role_condition(roles)
43
+ return {} unless roles
44
+
45
+ r = get_roles(roles)
46
+ ids = r.collect {|role| role.id}
47
+
48
+ return (ids.empty? ? {} : {Permit::Config.role_class.name.foreign_key => ids})
49
+ end
50
+
51
+ def resource_conditions(resource)
52
+ case resource
53
+ when :any then {}
54
+ when nil then {:resource_type => nil, :resource_id => nil}
55
+ else {:resource_type => resource.class.resource_type, :resource_id => resource.id}
56
+ end
57
+ end
58
+
59
+ def get_role(role)
60
+ role.is_a?(Permit::Config.role_class) ? role : Permit::Config.role_class.find_by_key(role)
61
+ end
62
+
63
+ def get_roles(roles)
64
+ permit_arrayify(roles).collect {|r| get_role(r)}.compact
65
+ end
66
+ end
67
+ end
data/lib/permit.rb ADDED
@@ -0,0 +1,134 @@
1
+ require File.dirname(__FILE__) + '/permit/support'
2
+ require File.dirname(__FILE__) + '/permit/permit_rule'
3
+ require File.dirname(__FILE__) + '/permit/permit_rules'
4
+ require File.dirname(__FILE__) + '/permit/controller'
5
+ require File.dirname(__FILE__) + '/models/association'
6
+ require File.dirname(__FILE__) + '/models/role'
7
+ require File.dirname(__FILE__) + '/models/person'
8
+ require File.dirname(__FILE__) + '/models/authorization'
9
+ require File.dirname(__FILE__) + '/models/authorizable'
10
+
11
+ module Permit
12
+ # allow|deny [:named_role|:person|:all|:guest|[]],
13
+ # (([:who|:that] => :method,)
14
+ # [:of|:on] => :instance_var,)
15
+ # :to => [:action|[]]
16
+ # (,:if => <method_name or proc>)
17
+ # (,:unless => <method_name or proc>)
18
+ #
19
+
20
+ # Raised when a {PermitRule} cannot be configured.
21
+ class PermitConfigurationError < StandardError; end
22
+
23
+ # Raised when an error occurs during evaluation of a {PermitRule}.
24
+ class PermitEvaluationError < StandardError; end
25
+
26
+ # Contains the configuration rules that Permit will apply during its
27
+ # processing.
28
+ #
29
+ # +role_class+, +authorization_class+, and +person_class+ are the model
30
+ # classes defined as representing their respective names by defining the
31
+ # corresponding <tt>permit_*</tt> method. +authorizable_classes+ is an array
32
+ # of all classes that are authorizable to roles by having defined
33
+ # +permit_authorizable+.
34
+ class Config
35
+ @@authorization_class, @@person_class, @@role_class = nil, nil, nil
36
+ @@models_defined = false
37
+ @@authorizable_classes = []
38
+ @@controller_subject_method = nil
39
+
40
+ @@action_aliases = {
41
+ :create => [:new, :create],
42
+ :update => [:edit, :update],
43
+ :destroy => [:delete, :destroy],
44
+ :read => [:index, :show],
45
+ :write => [:new, :create, :edit, :update]
46
+ }
47
+
48
+ # Indicates the response returned by {PermitRules#permitted?} when no rules
49
+ # match. If set to +:allow+ then the person will be granted access. If set
50
+ # to anything else, they will be denied.
51
+ @@default_access = :deny
52
+
53
+ class << self
54
+ # The class that currently represents authorizations in the system, as set
55
+ # by {set_core_models}.
56
+ def authorization_class; @@authorization_class; end
57
+ # The class that currently represents authorization subjects in the
58
+ # system, as set by {set_core_models}.
59
+ def person_class; @@person_class; end
60
+ # The class that curretly represents roles in the system, as set by
61
+ # {set_core_models}.
62
+ def role_class; @@role_class; end
63
+ # Classes that are marked as authorizable resources using
64
+ # {Permit::Models::AuthorizableExtensions::AuthorizableClassMethods#permit_authorizable permit_authorizable}.
65
+ def authorizable_classes; @@authorizable_classes; end
66
+
67
+ # Actions that when given to {PermitRules#allow}, and {PermitRules#deny}
68
+ # will be expanded into the actions given in the value array.
69
+ def action_aliases; @@action_aliases; end
70
+
71
+ # Indicates the response that PermitRules will take if no
72
+ # authorizations match. If set to +:allow+ then a subject will be given
73
+ # access unless denied. By default this is set to +:deny+
74
+ #
75
+ # @return the current default access.
76
+ def default_access; @@default_access; end
77
+
78
+ # Sets the response that PermitRules will use when no rules match.
79
+ #
80
+ # @param [:allow, :deny] access the default response to use.
81
+ def default_access=(access); @@default_access = access; end
82
+
83
+ # The method to use to retrieve the current authorization subject when
84
+ # rules are being evaluated. If nil, then the method will be inferred from
85
+ # the subject set in the call to {set_core_models}.
86
+ #
87
+ # @return [Symbol, nil]
88
+ def controller_subject_method; @@controller_subject_method; end
89
+
90
+ # Sets the name of the method to use to retrieve the current subject
91
+ # while checking authorizations. Set to nil, to infer the value from the
92
+ # subject set in {set_core_models}, or :current_person if named
93
+ # authorizations are not being used.
94
+ #
95
+ # @param [nil, Symbol] method a symbol representing the method to use.
96
+ def controller_subject_method=(method); @@controller_subject_method = method; end
97
+
98
+ # Sets the core authorization, person, and role models to be used for
99
+ # named authorizations, and configures them with their respective permit_*
100
+ # methods.
101
+ #
102
+ # @param [Class] authorization an ActiveRecord model representing
103
+ # authorizations.
104
+ # @param [Class] person an ActiveRecord model representing
105
+ # people.
106
+ # @param [Class] role an ActiveRecord model representing
107
+ # roles.
108
+ def set_core_models(authorization, person, role)
109
+ #raise PermitConfigurationError, "Core models cannot be redefined." if @@models_defined
110
+
111
+ @@authorization_class = authorization
112
+ @@person_class = person
113
+ @@role_class = role
114
+
115
+ @@authorization_class.send :permit_authorization
116
+ @@person_class.send :permit_person
117
+ @@role_class.send :permit_role
118
+ end
119
+
120
+ # Forces Permit to reload its core classes based off of those given in the
121
+ # initial call to Permit::Config.set_core_models. This is primarily needed
122
+ # so that Permit will work in Rails development mode because of class
123
+ # caching/reloading. These variables hang onto the original models as they
124
+ # were defined and end up in a weird state. Production does not experience
125
+ # this problem.
126
+ def reset_core_models
127
+ authz = Object.const_get authorization_class.name
128
+ person = Object.const_get person_class.name
129
+ role = Object.const_get role_class.name
130
+ Permit::Config.set_core_models(authz, person, role)
131
+ end
132
+ end
133
+ end
134
+ end
data/permit.gemspec ADDED
@@ -0,0 +1,91 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{permit}
8
+ s.version = "0.9.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Steve Valaitis"]
12
+ s.date = %q{2010-03-27}
13
+ s.email = %q{steve@digitalnothing.com}
14
+ s.extra_rdoc_files = [
15
+ "README.mkd"
16
+ ]
17
+ s.files = [
18
+ ".gitignore",
19
+ ".yardopts",
20
+ "MIT-LICENSE",
21
+ "README.mkd",
22
+ "Rakefile",
23
+ "VERSION.yml",
24
+ "generators/permit/USAGE",
25
+ "generators/permit/permit_generator.rb",
26
+ "generators/permit/templates/authorization.rb",
27
+ "generators/permit/templates/initializer.rb",
28
+ "generators/permit/templates/migration.rb",
29
+ "generators/permit/templates/role.rb",
30
+ "init.rb",
31
+ "install.rb",
32
+ "lib/models/association.rb",
33
+ "lib/models/authorizable.rb",
34
+ "lib/models/authorization.rb",
35
+ "lib/models/person.rb",
36
+ "lib/models/role.rb",
37
+ "lib/permit.rb",
38
+ "lib/permit/controller.rb",
39
+ "lib/permit/permit_rule.rb",
40
+ "lib/permit/permit_rules.rb",
41
+ "lib/permit/support.rb",
42
+ "permit.gemspec",
43
+ "rails/init.rb",
44
+ "spec/models/alternate_models_spec.rb",
45
+ "spec/models/authorizable_spec.rb",
46
+ "spec/models/authorization_spec.rb",
47
+ "spec/models/person_spec.rb",
48
+ "spec/models/role_spec.rb",
49
+ "spec/permit/controller_spec.rb",
50
+ "spec/permit/permit_rule_spec.rb",
51
+ "spec/permit/permit_rules_spec.rb",
52
+ "spec/permit_spec.rb",
53
+ "spec/spec_helper.rb",
54
+ "spec/support/helpers.rb",
55
+ "spec/support/models.rb",
56
+ "spec/support/permits_controller.rb",
57
+ "tasks/permit_tasks.rake",
58
+ "uninstall.rb"
59
+ ]
60
+ s.homepage = %q{http://github.com/dnd/permit}
61
+ s.rdoc_options = ["--charset=UTF-8"]
62
+ s.require_paths = ["lib"]
63
+ s.rubygems_version = %q{1.3.5}
64
+ s.summary = %q{A flexible authorization plugin for Ruby on Rails.}
65
+ s.test_files = [
66
+ "spec/spec_helper.rb",
67
+ "spec/support/helpers.rb",
68
+ "spec/support/models.rb",
69
+ "spec/support/permits_controller.rb",
70
+ "spec/models/alternate_models_spec.rb",
71
+ "spec/models/person_spec.rb",
72
+ "spec/models/role_spec.rb",
73
+ "spec/models/authorizable_spec.rb",
74
+ "spec/models/authorization_spec.rb",
75
+ "spec/permit_spec.rb",
76
+ "spec/permit/permit_rules_spec.rb",
77
+ "spec/permit/controller_spec.rb",
78
+ "spec/permit/permit_rule_spec.rb"
79
+ ]
80
+
81
+ if s.respond_to? :specification_version then
82
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
83
+ s.specification_version = 3
84
+
85
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
86
+ else
87
+ end
88
+ else
89
+ end
90
+ end
91
+
data/rails/init.rb ADDED
@@ -0,0 +1,7 @@
1
+ #require 'permit'
2
+
3
+ ActiveRecord::Base.send :include,
4
+ Permit::Models::AuthorizationExtensions,
5
+ Permit::Models::AuthorizableExtensions,
6
+ Permit::Models::RoleExtensions,
7
+ Permit::Models::PersonExtensions
@@ -0,0 +1,54 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ module Permit::Specs
4
+ class User < ActiveRecord::Base; end
5
+ class Entitlement < ActiveRecord::Base; end
6
+ class Job < ActiveRecord::Base; end
7
+
8
+ describe "Alternate core models" do
9
+ before :all do
10
+ @auth = Permit::Config.authorization_class
11
+ @person = Permit::Config.person_class
12
+ @role = Permit::Config.role_class
13
+
14
+ Permit::Config.set_core_models(Permit::Specs::Entitlement, Permit::Specs::User, Permit::Specs::Job)
15
+ end
16
+
17
+ before do
18
+ @sam = User.create! :name => 'sam'
19
+ @superapp = Project.create! :name => 'superapp'
20
+ @lameapp = Project.create! :name => 'lameapp'
21
+ @dev = Job.create! :key => :developer, :name => 'developer'
22
+ @tester = Job.create! :key => :tester, :name => 'tester'
23
+
24
+ @sam.authorize [@dev, @tester], @superapp
25
+ @sam.authorize @dev, @lameapp
26
+ end
27
+
28
+ it "subject model should have an entitlements association" do
29
+ @sam.should have(3).entitlements
30
+ end
31
+
32
+ it "role model should have an entitlements association" do
33
+ @dev.should have(2).entitlements
34
+ end
35
+
36
+ describe "entitlements association" do
37
+ it "should have a users_as method" do
38
+ @dev.entitlements.users_as(@dev).should have(1).item
39
+ end
40
+
41
+ it "should have a users_for method" do
42
+ @dev.entitlements.users_for(@superapp).should have(1).item
43
+ end
44
+
45
+ it "should have a jobs_for method" do
46
+ @sam.entitlements.jobs_for(@superapp).should have(2).item
47
+ end
48
+ end
49
+
50
+ after :all do
51
+ Permit::Config.set_core_models(@auth, @person, @role)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,78 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ module Permit::Specs
4
+ describe "Permit::Models::Authorizable#permit_authorizable" do
5
+ describe "defined method self#resource_type" do
6
+ it "should return the db resource_type value" do
7
+ Project.resource_type.should == 'Permit::Specs::Project'
8
+ end
9
+ end
10
+
11
+ context "created instance methods" do
12
+ before do
13
+ @bob = Person.create :name => "bob"
14
+ @tom = Person.create :name => "tom"
15
+ @hotness = Project.create(:name => "hotness")
16
+ @maintenance = Project.create(:name => "maintenance")
17
+ Role.create :key => :site_admin, :name => 'site admin', :authorize_resource => false, :requires_resource => false
18
+ new_authz @bob, :site_admin, nil
19
+ new_authz @bob, :admin, @maintenance
20
+ new_authz @bob, :developer, @maintenance
21
+ new_authz @bob, :team_lead, @hotness
22
+
23
+ new_authz @tom, :admin, @hotness
24
+ end
25
+
26
+ context "authorizations" do
27
+ it "should have all authorizations that apply to the current resource" do
28
+ @hotness.should have(2).authorizations
29
+ @hotness.authorizations[0].person.should == @bob
30
+ @hotness.authorizations[0].role.key.should == 'team_lead'
31
+ @hotness.authorizations[1].person.should == @tom
32
+ @hotness.authorizations[1].role.key.should == 'admin'
33
+ end
34
+ end
35
+
36
+ context "for finding authorizations for given roles" do
37
+ before do
38
+ @bob = Person.create :name => "bob"
39
+ @tom = Person.create :name => "tom"
40
+ @hotness = Project.create(:name => "hotness")
41
+ @maintenance = Project.create(:name => "maintenance")
42
+ Role.create :key => :site_admin, :name => 'site admin', :authorize_resource => false, :requires_resource => false
43
+ new_authz @bob, :site_admin, nil
44
+ new_authz @bob, :admin, @maintenance
45
+ new_authz @bob, :developer, @maintenance
46
+ new_authz @bob, :team_lead, @hotness
47
+
48
+ new_authz @tom, :admin, @hotness
49
+ end
50
+
51
+ it "#authorizations.people_as should return the people as the roles for the current resource" do
52
+ @jack = Person.create :name => 'jack'
53
+ @jack.authorize :admin, @maintenance
54
+ people = @maintenance.authorizations.people_as :admin
55
+ people.should have(2).items
56
+ people[0].should == @bob
57
+ people[1].should == @jack
58
+ end
59
+
60
+ it "#authorizations.people_as should not contain duplicates" do
61
+ people = @maintenance.authorizations.people_as [:admin, :developer]
62
+ people.should have(1).item
63
+ people[0].should == @bob
64
+ end
65
+
66
+ it "#authorizations.as should return the authorizations for given roles on the current resource" do
67
+ authz = @maintenance.authorizations.as [:admin, :developer]
68
+ authz.should have(2).items
69
+ authz[0].person.should == @bob
70
+ authz[0].role.key.should == 'admin'
71
+ authz[1].person.should == @bob
72
+ authz[1].role.key.should == 'developer'
73
+
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,77 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ module Permit::Specs
4
+ describe "Permit::Models::AuthorizationExtensions#permit_authorization" do
5
+ describe "validations" do
6
+ it "should be invalid without a role" do
7
+ a = Authorization.new
8
+ a.should_not be_valid
9
+ a.should have(1).error_on(:role)
10
+ a.errors[:role].should == "can't be blank"
11
+ end
12
+
13
+ it "should be invalid without a person" do
14
+ a = Authorization.new
15
+ a.should_not be_valid
16
+ a.should have(1).error_on(:person)
17
+ a.errors[:person].should == "can't be blank"
18
+ end
19
+
20
+ it "should be invalid without a resource if the role requires one" do
21
+ r = Role.new :requires_resource => true
22
+ a = Authorization.new :role => r
23
+ a.should_not be_valid
24
+ a.should have(1).error_on(:resource)
25
+ a.errors[:resource].should == "can't be blank"
26
+ end
27
+
28
+ it "should be invalid with a resource if the role doesn't allow one" do
29
+ r = Role.new :authorize_resource => false, :requires_resource => false
30
+ a = Authorization.new :role => r
31
+ a.resource = Project.create :name => "First"
32
+
33
+ a.should_not be_valid
34
+ a.should have(1).error_on(:resource)
35
+ a.errors[:resource].should == "Specific resources may not be granted for this role."
36
+ end
37
+
38
+ it "should allow a resource if the role allows but doesn't require a resource" do
39
+ r = Role.new :authorize_resource => true, :requires_resource => false
40
+ a = Authorization.new :role => r
41
+ a.resource = Project.create :name => "First"
42
+
43
+ a.valid?
44
+ a.should have(0).errors_on(:resource)
45
+ end
46
+
47
+ it "should allow no resource if the role allows but doesn't require a resource" do
48
+ r = Role.new :authorize_resource => true, :requires_resource => false
49
+ a = Authorization.new :role => r
50
+
51
+ a.valid?
52
+ a.should have(0).errors_on(:resource)
53
+ end
54
+
55
+ it "should be invalid if the person is already authorized for the given role and resource" do
56
+ p = Person.create :name => 'bob'
57
+ r = Role.create :key => :admin, :name => "Admin"
58
+ project = Project.create! :name => "First"
59
+ Authorization.create! :role => r, :person => p, :resource => project
60
+
61
+ a = Authorization.new :role => r, :person => p, :resource => project
62
+ a.should_not be_valid
63
+ a.should have(1).error_on(:role)
64
+ a.errors[:role].should == "This person is already authorized for this resource"
65
+ end
66
+
67
+ it "should be valid with valid attributes" do
68
+ p = Person.create :name => 'bob'
69
+ r = Role.create :key => :admin, :name => "Admin"
70
+ a = Authorization.new :role => r, :person => p
71
+ a.resource = Project.create :name => "First"
72
+
73
+ a.save.should be_true
74
+ end
75
+ end
76
+ end
77
+ end