zuul 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/lib/generators/zuul/orm_helpers.rb +21 -0
  2. data/lib/generators/zuul/permission_generator.rb +57 -0
  3. data/lib/generators/zuul/permission_role_generator.rb +40 -0
  4. data/lib/generators/zuul/permission_subject_generator.rb +40 -0
  5. data/lib/generators/zuul/role_generator.rb +58 -0
  6. data/lib/generators/zuul/role_subject_generator.rb +40 -0
  7. data/lib/generators/zuul/subject_generator.rb +39 -0
  8. data/lib/generators/zuul/templates/permission.rb +18 -0
  9. data/lib/generators/zuul/templates/permission_existing.rb +25 -0
  10. data/lib/generators/zuul/templates/permission_role.rb +17 -0
  11. data/lib/generators/zuul/templates/permission_role_existing.rb +24 -0
  12. data/lib/generators/zuul/templates/permission_subject.rb +17 -0
  13. data/lib/generators/zuul/templates/permission_subject_existing.rb +24 -0
  14. data/lib/generators/zuul/templates/role.rb +20 -0
  15. data/lib/generators/zuul/templates/role_existing.rb +27 -0
  16. data/lib/generators/zuul/templates/role_subject.rb +17 -0
  17. data/lib/generators/zuul/templates/role_subject_existing.rb +24 -0
  18. data/lib/tasks/zuul.rake +56 -0
  19. data/lib/zuul.rb +14 -5
  20. data/lib/zuul/action_controller.rb +108 -0
  21. data/lib/zuul/action_controller/dsl.rb +384 -0
  22. data/lib/zuul/action_controller/evaluators.rb +60 -0
  23. data/lib/zuul/active_record.rb +338 -0
  24. data/lib/zuul/active_record/context.rb +38 -0
  25. data/lib/zuul/active_record/permission.rb +31 -0
  26. data/lib/zuul/active_record/permission_role.rb +29 -0
  27. data/lib/zuul/active_record/permission_subject.rb +29 -0
  28. data/lib/zuul/active_record/role.rb +117 -0
  29. data/lib/zuul/active_record/role_subject.rb +29 -0
  30. data/lib/zuul/active_record/scope.rb +71 -0
  31. data/lib/zuul/active_record/subject.rb +239 -0
  32. data/lib/zuul/configuration.rb +149 -0
  33. data/lib/zuul/context.rb +53 -0
  34. data/lib/zuul/exceptions.rb +3 -0
  35. data/lib/zuul/exceptions/access_denied.rb +9 -0
  36. data/lib/zuul/exceptions/invalid_context.rb +9 -0
  37. data/lib/zuul/exceptions/undefined_scope.rb +9 -0
  38. data/lib/zuul/railtie.rb +5 -0
  39. data/lib/zuul/version.rb +3 -0
  40. data/lib/zuul_viz.rb +195 -0
  41. data/spec/db/schema.rb +172 -0
  42. data/spec/spec_helper.rb +25 -0
  43. data/spec/support/capture_stdout.rb +12 -0
  44. data/spec/support/models.rb +167 -0
  45. data/spec/zuul/active_record/context_spec.rb +55 -0
  46. data/spec/zuul/active_record/permission_role_spec.rb +84 -0
  47. data/spec/zuul/active_record/permission_spec.rb +174 -0
  48. data/spec/zuul/active_record/permission_subject_spec.rb +84 -0
  49. data/spec/zuul/active_record/role_spec.rb +694 -0
  50. data/spec/zuul/active_record/role_subject_spec.rb +84 -0
  51. data/spec/zuul/active_record/scope_spec.rb +75 -0
  52. data/spec/zuul/active_record/subject_spec.rb +1186 -0
  53. data/spec/zuul/active_record_spec.rb +624 -0
  54. data/spec/zuul/configuration_spec.rb +254 -0
  55. data/spec/zuul/context_spec.rb +128 -0
  56. data/spec/zuul_spec.rb +15 -0
  57. metadata +181 -70
  58. data/.document +0 -5
  59. data/.gitignore +0 -23
  60. data/LICENSE +0 -20
  61. data/README.rdoc +0 -65
  62. data/Rakefile +0 -54
  63. data/VERSION +0 -1
  64. data/lib/zuul/restrict_access.rb +0 -104
  65. data/lib/zuul/valid_roles.rb +0 -37
  66. data/spec/rails_root/app/controllers/application_controller.rb +0 -2
  67. data/spec/rails_root/app/models/user.rb +0 -8
  68. data/spec/rails_root/config/boot.rb +0 -110
  69. data/spec/rails_root/config/database.yml +0 -5
  70. data/spec/rails_root/config/environment.rb +0 -7
  71. data/spec/rails_root/config/environments/test.rb +0 -7
  72. data/spec/rails_root/config/initializers/session_store.rb +0 -15
  73. data/spec/rails_root/config/routes.rb +0 -4
  74. data/spec/rails_root/db/test.sqlite3 +0 -0
  75. data/spec/rails_root/log/test.log +0 -5388
  76. data/spec/rails_root/spec/controllers/require_user_spec.rb +0 -138
  77. data/spec/rails_root/spec/controllers/restrict_access_spec.rb +0 -64
  78. data/spec/rails_root/spec/models/user_spec.rb +0 -37
  79. data/spec/rails_root/spec/spec_helper.rb +0 -34
  80. data/zuul.gemspec +0 -78
@@ -0,0 +1,29 @@
1
+ module Zuul
2
+ module ActiveRecord
3
+ module PermissionRole
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ base.send :include, ContextMethods # defined in lib/zuul/active_record.rb
7
+ end
8
+
9
+ module ClassMethods
10
+ def self.extended(base)
11
+ base.send :attr_accessible, :context, :context_id, :context_type, base.auth_scope.permission_foreign_key.to_sym, base.auth_scope.role_foreign_key.to_sym
12
+ add_validations base
13
+ add_associations base
14
+ end
15
+
16
+ def self.add_validations(base)
17
+ base.send :validates_presence_of, base.auth_scope.permission_foreign_key.to_sym, base.auth_scope.role_foreign_key.to_sym
18
+ base.send :validates_uniqueness_of, base.auth_scope.permission_foreign_key.to_sym, :scope => [base.auth_scope.role_foreign_key.to_sym, :context_id, :context_type], :case_sensitive => false
19
+ base.send :validates_numericality_of, base.auth_scope.permission_foreign_key.to_sym, base.auth_scope.role_foreign_key.to_sym, :only_integer => true
20
+ end
21
+
22
+ def self.add_associations(base)
23
+ base.send :belongs_to, base.auth_scope.permission_class_name.underscore.to_sym
24
+ base.send :belongs_to, base.auth_scope.role_class_name.underscore.to_sym
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ module Zuul
2
+ module ActiveRecord
3
+ module PermissionSubject
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ base.send :include, ContextMethods # defined in lib/zuul/active_record.rb
7
+ end
8
+
9
+ module ClassMethods
10
+ def self.extended(base)
11
+ base.send :attr_accessible, :context, :context_id, :context_type, base.auth_scope.permission_foreign_key.to_sym, base.auth_scope.subject_foreign_key.to_sym
12
+ add_validations base
13
+ add_associations base
14
+ end
15
+
16
+ def self.add_validations(base)
17
+ base.send :validates_presence_of, base.auth_scope.permission_foreign_key.to_sym, base.auth_scope.subject_foreign_key.to_sym
18
+ base.send :validates_uniqueness_of, base.auth_scope.permission_foreign_key.to_sym, :scope => [base.auth_scope.subject_foreign_key.to_sym, :context_id, :context_type], :case_sensitive => false
19
+ base.send :validates_numericality_of, base.auth_scope.permission_foreign_key.to_sym, base.auth_scope.subject_foreign_key.to_sym, :only_integer => true
20
+ end
21
+
22
+ def self.add_associations(base)
23
+ base.send :belongs_to, base.auth_scope.permission_class_name.underscore.to_sym
24
+ base.send :belongs_to, base.auth_scope.subject_class_name.underscore.to_sym
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,117 @@
1
+ module Zuul
2
+ module ActiveRecord
3
+ module Role
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ base.send :include, ContextMethods # defined in lib/zuul/active_record.rb
7
+ base.send :include, PermissionMethods if base.auth_scope.config.with_permissions
8
+ end
9
+
10
+ module ClassMethods
11
+ def self.extended(base)
12
+ base.send :attr_accessible, :context_id, :context_type, :level, :slug
13
+ add_validations base
14
+ add_associations base
15
+ end
16
+
17
+ def self.add_validations(base)
18
+ base.send :validates_presence_of, :level, :slug
19
+ base.send :validates_uniqueness_of, :slug, :scope => [:context_id, :context_type], :case_sensitive => false
20
+ base.send :validates_format_of, :slug, :with => /\A[a-z0-9_]+\Z/
21
+ base.send :validates_uniqueness_of, :level, :scope => [:context_id, :context_type]
22
+ base.send :validates_numericality_of, :level, :only_integer => true
23
+ end
24
+
25
+ def self.add_associations(base)
26
+ base.send :has_many, base.auth_scope.role_subjects_table_name.to_sym
27
+ base.send :has_many, base.auth_scope.subjects_table_name.to_sym, :through => base.auth_scope.role_subjects_table_name.to_sym
28
+ if base.auth_scope.config.with_permissions
29
+ base.send :has_many, base.auth_scope.permission_roles_table_name.to_sym
30
+ base.send :has_many, base.auth_scope.permissions_table_name.to_sym, :through => base.auth_scope.permission_roles_table_name.to_sym
31
+ end
32
+ end
33
+ end
34
+
35
+ module PermissionMethods
36
+ # Assigns a permission to a role within the provided context.
37
+ #
38
+ # If a Permission object is provided it's used directly, otherwise if a
39
+ # permission slug is provided, the permission is looked up in the context
40
+ # chain by target_permission.
41
+ def assign_permission(permission, context=nil, force_context=nil)
42
+ auth_scope do
43
+ context = Zuul::Context.parse(context)
44
+ target = target_permission(permission, context, force_context)
45
+ return false unless verify_target_context(target, context, force_context) && verify_target_context(self, context, false) && permission_role_class.where(role_foreign_key.to_sym => id, permission_foreign_key.to_sym => target.id, :context_type => context.class_name, :context_id => context.id).limit(1).first.nil?
46
+
47
+ return permission_role_class.create(role_foreign_key.to_sym => id, permission_foreign_key.to_sym => target.id, :context_type => context.class_name, :context_id => context.id)
48
+ end
49
+ end
50
+
51
+ # Removes a permission from a role within the provided context.
52
+ #
53
+ # If a Permission object is provided it's used directly, otherwise if a
54
+ # permission slug is provided, the permission is looked up in the context
55
+ # chain by target_permission.
56
+ def unassign_permission(permission, context=nil, force_context=nil)
57
+ auth_scope do
58
+ context = Zuul::Context.parse(context)
59
+ target = target_permission(permission, context, force_context)
60
+ return false if target.nil?
61
+
62
+ assigned_permission = permission_role_class.where(role_foreign_key.to_sym => id, permission_foreign_key.to_sym => target.id, :context_type => context.class_name, :context_id => context.id).limit(1).first
63
+ return false if assigned_permission.nil?
64
+ return assigned_permission.destroy
65
+ end
66
+ end
67
+ alias_method :remove_permission, :unassign_permission
68
+
69
+ # Checks whether a role has a permission within the provided context.
70
+ #
71
+ # If a Permission object is provided it's used directly, otherwise if a
72
+ # permission slug is provided, the permission is looked up in the context
73
+ # chain by target_permission.
74
+ #
75
+ # The assigned context behaves the same way, in that if the permission is not found
76
+ # to belong to the role with the specified context, we look up the context chain.
77
+ #
78
+ # TODO add options to force context, not go up the chain
79
+ def has_permission?(permission, context=nil, force_context=nil)
80
+ auth_scope do
81
+ force_context ||= config.force_context
82
+ context = Zuul::Context.parse(context)
83
+ target = target_permission(permission, context, force_context)
84
+ return false if target.nil?
85
+
86
+ return true unless (context.id.nil? && !force_context) || permission_role_class.where(role_foreign_key.to_sym => id, permission_foreign_key.to_sym => target.id, :context_type => context.class_name, :context_id => context.id).first.nil?
87
+ unless force_context
88
+ return true unless context.class_name.nil? || permission_role_class.where(role_foreign_key.to_sym => id, permission_foreign_key.to_sym => target.id, :context_type => context.class_name, :context_id => nil).first.nil?
89
+ return !permission_role_class.where(role_foreign_key.to_sym => id, permission_foreign_key.to_sym => target.id, :context_type => nil, :context_id => nil).first.nil?
90
+ end
91
+ end
92
+ end
93
+ alias_method :permission?, :has_permission?
94
+ alias_method :can?, :has_permission?
95
+ alias_method :allowed_to?, :has_permission?
96
+
97
+ # Returns all permissions possessed by the role within the provided context.
98
+ def permissions_for(context=nil, force_context=nil)
99
+ auth_scope do
100
+ force_context ||= config.force_context
101
+ context = Zuul::Context.parse(context)
102
+ if force_context
103
+ return permission_class.joins(permission_roles_table_name.to_sym).where(permission_roles_table_name.to_sym => {role_foreign_key.to_sym => id, :context_type => context.class_name, :context_id => context.id})
104
+ else
105
+ return permission_class.joins("LEFT JOIN #{permission_roles_table_name} ON #{permission_roles_table_name}.#{permission_foreign_key} = #{permissions_table_name}.id").where("#{permission_roles_table_name}.#{role_foreign_key} = ? AND (#{permission_roles_table_name}.context_type #{sql_is_or_equal(context.class_name)} ? OR #{permission_roles_table_name}.context_type IS NULL) AND (#{permission_roles_table_name}.context_id #{sql_is_or_equal(context.id)} ? OR #{permission_roles_table_name}.context_id IS NULL)", id, context.class_name, context.id)
106
+ end
107
+ end
108
+ end
109
+
110
+ # Check whether the role possesses any permissions within the specified context.
111
+ def permissions_for?(context=nil, force_context=nil)
112
+ permissions_for(context, force_context).count > 0
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,29 @@
1
+ module Zuul
2
+ module ActiveRecord
3
+ module RoleSubject
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ base.send :include, ContextMethods # defined in lib/zuul/active_record.rb
7
+ end
8
+
9
+ module ClassMethods
10
+ def self.extended(base)
11
+ base.send :attr_accessible, :context, :context_id, :context_type, base.auth_scope.role_foreign_key.to_sym, base.auth_scope.subject_foreign_key.to_sym
12
+ add_validations base
13
+ add_associations base
14
+ end
15
+
16
+ def self.add_validations(base)
17
+ base.send :validates_presence_of, base.auth_scope.role_foreign_key.to_sym, base.auth_scope.subject_foreign_key.to_sym
18
+ base.send :validates_uniqueness_of, base.auth_scope.role_foreign_key.to_sym, :scope => [base.auth_scope.subject_foreign_key.to_sym, :context_id, :context_type], :case_sensitive => false
19
+ base.send :validates_numericality_of, base.auth_scope.role_foreign_key.to_sym, base.auth_scope.subject_foreign_key.to_sym, :only_integer => true
20
+ end
21
+
22
+ def self.add_associations(base)
23
+ base.send :belongs_to, base.auth_scope.role_class_name.underscore.to_sym
24
+ base.send :belongs_to, base.auth_scope.subject_class_name.underscore.to_sym
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,71 @@
1
+ module Zuul
2
+ module ActiveRecord
3
+ class Scope
4
+ attr_reader :config, :name
5
+
6
+ protected
7
+
8
+ def initialize(config)
9
+ @config = config
10
+ @name = @config.scope
11
+ define_reflection_methods
12
+ super()
13
+ end
14
+
15
+ # Define dynamic reflection methods that reference the config to be used for subjects, roles, permissions and their associations.
16
+ def define_reflection_methods
17
+ # *_class_name, *_class, *_table_name methods for all classes
18
+ @config.classes.to_h.each do |class_type,class_name|
19
+ class_type_name = class_type.to_s.gsub(/_class$/,'').singularize
20
+ class_eval do
21
+ # def CLASS_TYPE_class_name
22
+ define_method "#{class_type_name}_class_name" do
23
+ if @config.send(class_type).is_a?(Class)
24
+ @config.send(class_type).name
25
+ else
26
+ @config.send(class_type).to_s.camelize
27
+ end
28
+ end
29
+ alias_method "#{class_type_name.pluralize}_class_name", "#{class_type_name}_class_name"
30
+
31
+ # def CLASS_TYPE_class
32
+ define_method "#{class_type_name}_class" do
33
+ "::#{send("#{class_type_name}_class_name")}".constantize
34
+ end
35
+ alias_method "#{class_type_name.pluralize}_class", "#{class_type_name}_class"
36
+
37
+ # def CLASS_TYPE_table_name
38
+ define_method "#{class_type_name}_table_name" do
39
+ send("#{class_type_name}_class").table_name
40
+ end
41
+ alias_method "#{class_type_name.pluralize}_table_name", "#{class_type_name}_table_name"
42
+
43
+ unless class_type.to_s.underscore == "#{class_name.to_s.underscore}_class"
44
+ ["_class_name", "_class", "_table_name"].each do |suffix|
45
+ alias_method "#{class_name.to_s.underscore.singularize}#{suffix}", "#{class_type_name}#{suffix}"
46
+ alias_method "#{class_name.to_s.underscore.pluralize}#{suffix}", "#{class_name.to_s.underscore.singularize}#{suffix}"
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ # *_foreign_key method for primary classes
53
+ @config.primary_classes.to_h.each do |class_type,class_name|
54
+ class_type_name = class_type.to_s.gsub(/_class$/,'').singularize
55
+ class_eval do
56
+ # def CLASS_TYPE_foreign_key
57
+ define_method "#{class_type_name}_foreign_key" do
58
+ "#{send("#{class_type_name}_table_name").singularize}_#{send("#{class_type_name}_class").primary_key}"
59
+ end
60
+ alias_method "#{class_type.to_s.gsub(/_class$/,"").pluralize}_foreign_key", "#{class_type.to_s.gsub(/_class$/,"").singularize}_foreign_key"
61
+
62
+ unless class_type.to_s.underscore == "#{class_name.to_s.underscore}_class"
63
+ alias_method "#{class_name.to_s.underscore.singularize}_foreign_key", "#{class_type.to_s.gsub(/_class$/,"").singularize}_foreign_key" # CLASS_NAME_foreign_key
64
+ alias_method "#{class_name.to_s.underscore.pluralize}_foreign_key", "#{class_name.to_s.underscore.singularize}_foreign_key"
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,239 @@
1
+ module Zuul
2
+ module ActiveRecord
3
+ module Subject
4
+ def self.included(base)
5
+ base.send :include, RoleMethods
6
+ base.send(:include, PermissionMethods) if base.auth_scope.config.with_permissions
7
+ end
8
+
9
+ module RoleMethods
10
+ def self.included(base)
11
+ base.send :extend, ClassMethods
12
+ base.send :include, InstanceMethods
13
+ end
14
+
15
+ module ClassMethods
16
+ def self.extended(base)
17
+ base.send :has_many, base.auth_scope.role_subjects_table_name.to_sym
18
+ base.send :has_many, base.auth_scope.roles_table_name.to_sym, :through => base.auth_scope.role_subjects_table_name.to_sym
19
+ end
20
+ end
21
+
22
+ module InstanceMethods
23
+ # Assigns a role to a subject within the provided context.
24
+ #
25
+ # If a Role object is provided it's used directly, otherwise if a role slug
26
+ # is provided, the role is looked up in the context chain by target_role.
27
+ def assign_role(role, context=nil, force_context=nil)
28
+ auth_scope do
29
+ context = Zuul::Context.parse(context)
30
+ target = target_role(role, context, force_context)
31
+ return false unless verify_target_context(target, context, force_context) && role_subject_class.where(subject_foreign_key.to_sym => id, role_foreign_key.to_sym => target.id, :context_type => context.class_name, :context_id => context.id).limit(1).first.nil?
32
+
33
+ return role_subject_class.create(subject_foreign_key.to_sym => id, role_foreign_key.to_sym => target.id, :context_type => context.class_name, :context_id => context.id)
34
+ end
35
+ end
36
+
37
+ # Removes a role from a subject within the provided context.
38
+ #
39
+ # If a Role object is provided it's used directly, otherwise if a role slug
40
+ # is provided, the role is looked up in the context chain by target_role.
41
+ def unassign_role(role, context=nil, force_context=nil)
42
+ auth_scope do
43
+ context = Zuul::Context.parse(context)
44
+ target = target_role(role, context, force_context)
45
+ return false if target.nil?
46
+
47
+ assigned_role = role_subject_class.where(subject_foreign_key.to_sym => id, role_foreign_key.to_sym => target.id, :context_type => context.class_name, :context_id => context.id).limit(1).first
48
+ return false if assigned_role.nil?
49
+ return assigned_role.destroy
50
+ end
51
+ end
52
+ alias_method :remove_role, :unassign_role
53
+
54
+ # Checks whether a subject has a role within the provided context.
55
+ #
56
+ # If a Role object is provided it's used directly, otherwise if a role slug
57
+ # is provided, the role is looked up in the context chain by target_role.
58
+ #
59
+ # The assigned context behaves the same way, in that if the role is not found
60
+ # to belong to the subject with the specified context, we look up the context chain.
61
+ def has_role?(role, context=nil, force_context=nil)
62
+ auth_scope do
63
+ force_context ||= config.force_context
64
+ context = Zuul::Context.parse(context)
65
+ target = target_role(role, context, force_context)
66
+ return false if target.nil?
67
+
68
+ return true unless (context.id.nil? && !force_context) || role_subject_class.joins(role_class_name.underscore.to_sym).where(subject_foreign_key.to_sym => id, role_foreign_key.to_sym => target.id, :context_type => context.class_name, :context_id => context.id).first.nil?
69
+ return false if force_context
70
+ return true unless context.class_name.nil? || role_subject_class.where(subject_foreign_key.to_sym => id, role_foreign_key.to_sym => target.id, :context_type => context.class_name, :context_id => nil).first.nil?
71
+ return !role_subject_class.where(subject_foreign_key.to_sym => id, role_foreign_key.to_sym => target.id, :context_type => nil, :context_id => nil).first.nil?
72
+ end
73
+ end
74
+ alias_method :role?, :has_role?
75
+
76
+ # Checks whether a subject has the specified role or a role with a level greather than
77
+ # that of the specified role, within the provided context.
78
+ #
79
+ # If a Role object is provided it's used directly, otherwise if a role slug
80
+ # is provided, the role is looked up in the context chain by target_role.
81
+ #
82
+ # The assigned context behaves the same way, in that if a matching role is not found
83
+ # to belong to the subject with the specified context, we look up the context chain.
84
+ def has_role_or_higher?(role, context=nil, force_context=nil)
85
+ auth_scope do
86
+ context = Zuul::Context.parse(context)
87
+ target = target_role(role, context, force_context)
88
+ return false if target.nil?
89
+
90
+ return true if has_role?(target, context, force_context)
91
+
92
+ return true unless context.id.nil? || role_subject_class.joins(role_class_name.underscore.to_sym).where(subject_foreign_key.to_sym => id, :context_type => context.class_name, :context_id => context.id).where("#{roles_table_name}.level >= ? AND #{roles_table_name}.context_type #{sql_is_or_equal(target.context_type)} ? AND #{roles_table_name}.context_id #{sql_is_or_equal(target.context_id)} ?", target.level, target.context_type, target.context_id).first.nil?
93
+ return true unless context.class_name.nil? || role_subject_class.joins(role_class_name.underscore.to_sym).where(subject_foreign_key.to_sym => id, :context_type => context.class_name, :context_id => nil).where("#{roles_table_name}.level >= ? AND #{roles_table_name}.context_type #{sql_is_or_equal(target.context_type)} ? AND #{roles_table_name}.context_id #{sql_is_or_equal(target.context_id)} ?", target.level, target.context_type, target.context_id).first.nil?
94
+ return !role_subject_class.joins(role_class_name.underscore.to_sym).where(subject_foreign_key.to_sym => id, :context_type => nil, :context_id => nil).where("#{roles_table_name}.level >= ? AND #{roles_table_name}.context_type #{sql_is_or_equal(target.context_type)} ? AND #{roles_table_name}.context_id #{sql_is_or_equal(target.context_id)} ?", target.level, target.context_type, target.context_id).first.nil?
95
+ end
96
+ end
97
+ alias_method :role_or_higher?, :has_role_or_higher?
98
+ alias_method :at_least_role?, :has_role_or_higher?
99
+
100
+ # Returns the highest level role a subject possesses within the provided context.
101
+ #
102
+ # This includes any roles found by looking up the context chain.
103
+ def highest_role(context=nil, force_context=nil)
104
+ return nil unless roles_for?(context, force_context)
105
+ roles_for(context, force_context).order(:level).reverse_order.limit(1).first
106
+ end
107
+
108
+ # Returns all roles possessed by the subject within the provided context.
109
+ #
110
+ # This includes all roles found by looking up the context chain.
111
+ def roles_for(context=nil, force_context=nil)
112
+ auth_scope do
113
+ force_context ||= config.force_context
114
+ context = Zuul::Context.parse(context)
115
+ if force_context
116
+ return role_class.joins(role_subjects_table_name.to_sym).where("#{role_subjects_table_name}.#{subject_foreign_key} = ? AND #{role_subjects_table_name}.context_type #{sql_is_or_equal(context.class_name)} ? AND #{role_subjects_table_name}.context_id #{sql_is_or_equal(context.id)} ?", id, context.class_name, context.id)
117
+ else
118
+ return role_class.joins(role_subjects_table_name.to_sym).where("#{role_subjects_table_name}.#{subject_foreign_key} = ? AND ((#{role_subjects_table_name}.context_type #{sql_is_or_equal(context.class_name)} ? OR #{role_subjects_table_name}.context_type IS NULL) AND (#{role_subjects_table_name}.context_id #{sql_is_or_equal(context.id)} ? OR #{role_subjects_table_name}.context_id IS NULL))", id, context.class_name, context.id)
119
+ end
120
+ end
121
+ end
122
+
123
+ # Check whether the subject possesses any roles within the specified context.
124
+ #
125
+ # This includes any roles found by looking up the context chain.
126
+ def roles_for?(context=nil, force_context=nil)
127
+ roles_for(context, force_context).count > 0
128
+ end
129
+ end
130
+ end
131
+
132
+ module PermissionMethods
133
+ def self.included(base)
134
+ base.send :extend, ClassMethods
135
+ base.send :include, InstanceMethods
136
+ end
137
+
138
+ module ClassMethods
139
+ def self.extended(base)
140
+ base.send :has_many, base.auth_scope.permission_subjects_table_name.to_sym
141
+ base.send :has_many, base.auth_scope.permissions_table_name.to_sym, :through => base.auth_scope.permission_subjects_table_name.to_sym
142
+ end
143
+ end
144
+
145
+ module InstanceMethods
146
+ # Assigns a permission to a subject within the provided context.
147
+ #
148
+ # If a Permission object is provided it's used directly, otherwise if a
149
+ # permission slug is provided, the permission is looked up in the context
150
+ # chain by target_permission.
151
+ def assign_permission(permission, context=nil, force_context=nil)
152
+ auth_scope do
153
+ context = Zuul::Context.parse(context)
154
+ target = target_permission(permission, context, force_context)
155
+ return false unless verify_target_context(target, context, force_context) && permission_subject_class.where(subject_foreign_key.to_sym => id, permission_foreign_key.to_sym => target.id, :context_type => context.class_name, :context_id => context.id).limit(1).first.nil?
156
+
157
+ return permission_subject_class.create(subject_foreign_key.to_sym => id, permission_foreign_key.to_sym => target.id, :context_type => context.class_name, :context_id => context.id)
158
+ end
159
+ end
160
+
161
+ # Removes a permission from a subject within the provided context.
162
+ #
163
+ # If a Permission object is provided it's used directly, otherwise if a
164
+ # permission slug is provided, the permission is looked up in the context
165
+ # chain by target_permission.
166
+ def unassign_permission(permission, context=nil, force_context=nil)
167
+ auth_scope do
168
+ context = Zuul::Context.parse(context)
169
+ target = target_permission(permission, context, force_context)
170
+ return false if target.nil?
171
+
172
+ assigned_permission = permission_subject_class.where(subject_foreign_key.to_sym => id, permission_foreign_key.to_sym => target.id, :context_type => context.class_name, :context_id => context.id).limit(1).first
173
+ return false if assigned_permission.nil?
174
+ return assigned_permission.destroy
175
+ end
176
+ end
177
+ alias_method :remove_permission, :unassign_permission
178
+
179
+ # Checks whether a subject has a permission within the provided context.
180
+ #
181
+ # If a Permission object is provided it's used directly, otherwise if a
182
+ # permission slug is provided, the permission is looked up in the context
183
+ # chain by target_permission.
184
+ #
185
+ # The assigned context behaves the same way, in that if the permission is not found
186
+ # to belong to the subject with the specified context, we look up the context chain.
187
+ #
188
+ # Permissions belonging to roles possessed by the subject are also included.
189
+ def has_permission?(permission, context=nil, force_context=nil)
190
+ auth_scope do
191
+ force_context ||= config.force_context
192
+ context = Zuul::Context.parse(context)
193
+ target = target_permission(permission, context, force_context)
194
+ return false if target.nil?
195
+
196
+ return true unless (context.id.nil? && !force_context) || permission_subject_class.where(subject_foreign_key.to_sym => id, permission_foreign_key.to_sym => target.id, :context_type => context.class_name, :context_id => context.id).first.nil?
197
+ unless force_context
198
+ return true unless context.class_name.nil? || permission_subject_class.where(subject_foreign_key.to_sym => id, permission_foreign_key.to_sym => target.id, :context_type => context.class_name, :context_id => nil).first.nil?
199
+ return true unless permission_subject_class.where(subject_foreign_key.to_sym => id, permission_foreign_key.to_sym => target.id, :context_type => nil, :context_id => nil).first.nil?
200
+ end
201
+
202
+ return true unless (context.id.nil? && !force_context) || permission_role_class.where(role_foreign_key.to_sym => roles_for(context).map(&:id), permission_foreign_key.to_sym => target.id, :context_type => context.class_name, :context_id => context.id).first.nil?
203
+ return false if force_context
204
+ return true unless context.class_name.nil? || permission_role_class.where(role_foreign_key.to_sym => roles_for(context).map(&:id), permission_foreign_key.to_sym => target.id, :context_type => context.class_name, :context_id => nil).first.nil?
205
+ return !permission_role_class.where(role_foreign_key.to_sym => roles_for(context).map(&:id), permission_foreign_key.to_sym => target.id, :context_type => nil, :context_id => nil).first.nil?
206
+ end
207
+ end
208
+ alias_method :permission?, :has_permission?
209
+ alias_method :can?, :has_permission?
210
+ alias_method :allowed_to?, :has_permission?
211
+
212
+ # Returns all permissions possessed by the subject within the provided context.
213
+ #
214
+ # This includes permissions assigned directly to the subject or any roles possessed by
215
+ # the subject, as well as all permissions found by looking up the context chain.
216
+ def permissions_for(context=nil, force_context=nil)
217
+ auth_scope do
218
+ force_context ||= config.force_context
219
+ context = Zuul::Context.parse(context)
220
+ if force_context
221
+ return permission_class.joins("LEFT JOIN #{permission_roles_table_name} ON #{permission_roles_table_name}.#{permission_foreign_key} = #{permissions_table_name}.id LEFT JOIN #{permission_subjects_table_name} ON #{permission_subjects_table_name}.#{permission_foreign_key} = #{permissions_table_name}.id").where("(#{permission_subjects_table_name}.#{subject_foreign_key} = ? AND #{permission_subjects_table_name}.context_type #{sql_is_or_equal(context.class_name)} ? AND #{permission_subjects_table_name}.context_id #{sql_is_or_equal(context.id)} ?) OR (#{permission_roles_table_name}.#{role_foreign_key} IN (?) AND #{permission_roles_table_name}.context_type #{sql_is_or_equal(context.class_name)} ? AND #{permission_roles_table_name}.context_id #{sql_is_or_equal(context.id)} ?)", id, context.class_name, context.id, roles_for(context).map(&:id), context.class_name, context.id)
222
+ else
223
+ return permission_class.joins("LEFT JOIN #{permission_roles_table_name} ON #{permission_roles_table_name}.#{permission_foreign_key} = #{permissions_table_name}.id LEFT JOIN #{permission_subjects_table_name} ON #{permission_subjects_table_name}.#{permission_foreign_key} = #{permissions_table_name}.id").where("(#{permission_subjects_table_name}.#{subject_foreign_key} = ? AND (#{permission_subjects_table_name}.context_type #{sql_is_or_equal(context.class_name)} ? OR #{permission_subjects_table_name}.context_type IS NULL) AND (#{permission_subjects_table_name}.context_id #{sql_is_or_equal(context.id)} ? OR #{permission_subjects_table_name}.context_id IS NULL)) OR (#{permission_roles_table_name}.#{role_foreign_key} IN (?) AND (#{permission_roles_table_name}.context_type #{sql_is_or_equal(context.class_name)} ? OR #{permission_roles_table_name}.context_type IS NULL) AND (#{permission_roles_table_name}.context_id #{sql_is_or_equal(context.id)} ? OR #{permission_roles_table_name}.context_id IS NULL))", id, context.class_name, context.id, roles_for(context).map(&:id), context.class_name, context.id)
224
+ end
225
+ end
226
+ end
227
+
228
+ # Check whether the subject possesses any permissions within the specified context.
229
+ #
230
+ # This includes permissions assigned directly to the subject or any roles possessed by
231
+ # the subject, as well as all permissions found by looking up the context chain.
232
+ def permissions_for?(context=nil, force_context=nil)
233
+ permissions_for(context, force_context).count > 0
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
239
+ end