zuul 0.1.1 → 0.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.
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