skill_tree 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/lib/generators/skill_tree/install_generator.rb +20 -0
  3. data/lib/generators/skill_tree/orm_helpers.rb +14 -0
  4. data/lib/generators/skill_tree/templates/acl.rb +42 -0
  5. data/lib/generators/skill_tree/templates/create_skill_tree_acl_system.rb +48 -0
  6. data/lib/generators/skill_tree/templates/initializer.rb +1 -0
  7. data/lib/skill_tree.rb +15 -0
  8. data/lib/skill_tree/controller.rb +57 -0
  9. data/lib/skill_tree/errors.rb +5 -0
  10. data/lib/skill_tree/models.rb +10 -0
  11. data/lib/skill_tree/models/acl.rb +13 -0
  12. data/lib/skill_tree/models/acl_mapping.rb +16 -0
  13. data/lib/skill_tree/models/acl_ownership.rb +11 -0
  14. data/lib/skill_tree/models/permission.rb +10 -0
  15. data/lib/skill_tree/models/role.rb +22 -0
  16. data/lib/skill_tree/models/user_role.rb +14 -0
  17. data/lib/skill_tree/parser.rb +44 -0
  18. data/lib/skill_tree/parser/acl_parser.rb +42 -0
  19. data/lib/skill_tree/parser/role_parser.rb +57 -0
  20. data/lib/skill_tree/resource.rb +21 -0
  21. data/lib/skill_tree/resource/callbacks.rb +20 -0
  22. data/lib/skill_tree/resource/class_methods.rb +10 -0
  23. data/lib/skill_tree/resource/instance_methods.rb +52 -0
  24. data/lib/skill_tree/resource/relations.rb +18 -0
  25. data/lib/skill_tree/resource/scopes.rb +38 -0
  26. data/lib/skill_tree/subject.rb +53 -0
  27. data/lib/skill_tree/version.rb +3 -0
  28. data/spec/db/schema.rb +59 -0
  29. data/spec/skill_tree/controller_spec.rb +103 -0
  30. data/spec/skill_tree/generators/install_generator_spec.rb +34 -0
  31. data/spec/skill_tree/models/acl_mapping_spec.rb +46 -0
  32. data/spec/skill_tree/models/acl_ownership_spec.rb +30 -0
  33. data/spec/skill_tree/models/acl_spec.rb +32 -0
  34. data/spec/skill_tree/models/permission_spec.rb +16 -0
  35. data/spec/skill_tree/models/role_spec.rb +41 -0
  36. data/spec/skill_tree/models/user_role_spec.rb +16 -0
  37. data/spec/skill_tree/parser_spec.rb +71 -0
  38. data/spec/skill_tree/resource_spec.rb +137 -0
  39. data/spec/skill_tree/subject_spec.rb +65 -0
  40. data/spec/skill_tree_spec.rb +8 -0
  41. data/spec/spec_helper.rb +37 -0
  42. data/spec/support/acls/acl_example.rb +19 -0
  43. data/spec/support/acls/acl_example2.rb +24 -0
  44. data/spec/support/acls/acl_example3.rb +24 -0
  45. data/spec/support/acls/acl_example4.rb +24 -0
  46. data/spec/support/acls/subject_acl.rb +57 -0
  47. data/spec/support/capture_stdout.rb +12 -0
  48. data/spec/support/controllers.rb +71 -0
  49. data/spec/support/model_builder.rb +15 -0
  50. data/spec/support/models.rb +16 -0
  51. metadata +232 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d43026f65f8d1a50f6786da7a0dec94a57490768
4
+ data.tar.gz: 6bbca9232d594cac271824154e1785cb9d19c724
5
+ SHA512:
6
+ metadata.gz: 34f067818105a52dc4bd6e8ecba08ec800d2e8f67361cecf6c45fa9ddc6e6af6e8e72ee27d8c5cf2505093d6268de65b18af3e7f568cab136474596b288903c1
7
+ data.tar.gz: 3d75bde0f7769690cfb213ce3f07e96b3355042d423ecb84575019982ead06482af86d519fadebe63e42697b120f6fadc85104c7f8e9ba81e1ef57b73a716493
@@ -0,0 +1,20 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module SkillTree
5
+ class InstallGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+ source_root File.expand_path('../templates', __FILE__)
8
+
9
+ def self.next_migration_number(_path)
10
+ Time.now.utc.strftime('%Y%m%d%H%M%S')
11
+ end
12
+
13
+ def create_model_file
14
+ copy_file 'initializer.rb', 'config/initializers/skill_tree.rb'
15
+ copy_file 'acl.rb', 'config/acl.rb'
16
+ migration_template 'create_skill_tree_acl_system.rb',
17
+ 'db/migrate/create_skill_tree_acl_system.rb'
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ module SkillTree
2
+ module Generators
3
+ module OrmHelpers
4
+ def migration_exists?
5
+ Dir.glob("#{File.join(destination_root, migration_path)}/[0-9]*_*.rb")
6
+ .grep(/\d+_create_skill_tree_acl_system.rb$/).first
7
+ end
8
+
9
+ def migration_path
10
+ @migration_path ||= File.join('db', 'migrate')
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,42 @@
1
+ #
2
+ # Example acl.rb file
3
+ #
4
+
5
+ # acl :posts_acl, default_for: :posts do |a|
6
+ # a.role :guest do |r|
7
+ # r.can :read
8
+ # end
9
+
10
+ # a.role :user do |r|
11
+ # r.can :create, :read
12
+ # end
13
+
14
+ # a.role :editor do |r|
15
+ # r.inherit :user
16
+ # r.can :write
17
+ # end
18
+
19
+ # a.role :admin do |r|
20
+ # r.inherit :editor
21
+ # r.can :update, :delete
22
+ # end
23
+ # end
24
+
25
+ # acl :private_post do |a|
26
+ # a.role :guest do |r|
27
+ # end
28
+
29
+ # a.role :user do |r|
30
+ # r.can :create, :read
31
+ # end
32
+
33
+ # a.role :editor do |r|
34
+ # r.inherit :user
35
+ # r.can :read, :write
36
+ # end
37
+
38
+ # a.role :admin do |r|
39
+ # r.inherit :editor
40
+ # r.can :update, :delete
41
+ # end
42
+ # end
@@ -0,0 +1,48 @@
1
+ class CreateSkillTreeAclSystem < ActiveRecord::Migration
2
+ def change
3
+ create_table :acls do |t|
4
+ t.string :name, null: false
5
+ t.integer :version, null: false
6
+ end
7
+ add_index :acls, :name, unique: true
8
+
9
+ create_table :roles do |t|
10
+ t.string :name, null: false
11
+ end
12
+ add_index :roles, :name, unique: true
13
+
14
+ create_table :permissions do |t|
15
+ t.string :name, null: false
16
+ end
17
+ add_index :permissions, :name, unique: true
18
+
19
+ create_table :acl_mappings do |t|
20
+ t.references :acl, null: false
21
+ t.references :role, null: false
22
+ t.references :permission, null: true
23
+
24
+ t.timestamps null: false
25
+ end
26
+
27
+ add_index :acl_mappings, [:acl_id, :role_id, :permission_id], unique: true
28
+
29
+ create_table :user_roles do |t|
30
+ t.references :user
31
+ t.references :role, null: false
32
+ t.references :resource, polymorphic: true
33
+
34
+ t.timestamps null: false
35
+ end
36
+ add_index :user_roles, [:resource_type, :resource_id]
37
+ # Only can't be admin of a resource multiple times
38
+ add_index :user_roles, [:user_id, :role_id, :resource_type, :resource_id],
39
+ unique: true, name: :user_roles_avoid_duplicate_roles
40
+
41
+ create_table :acl_ownerships do |t|
42
+ t.references :acl, null: false
43
+ t.references :resource, polymorphic: true
44
+ end
45
+ # There can be only one-to-many relationship with a resource
46
+ add_index :acl_ownerships, [:resource_type, :resource_id], unique: true
47
+ end
48
+ end
@@ -0,0 +1 @@
1
+ SkillTree.init!
@@ -0,0 +1,15 @@
1
+ require 'skill_tree/controller.rb'
2
+ require 'skill_tree/errors.rb'
3
+ require 'skill_tree/models.rb'
4
+ require 'skill_tree/parser.rb'
5
+ require 'skill_tree/resource.rb'
6
+ require 'skill_tree/subject.rb'
7
+ require 'skill_tree/version.rb'
8
+
9
+ module SkillTree
10
+ class <<self
11
+ def init!
12
+ SkillTree::Parser::Initializer.parse!
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,57 @@
1
+ module SkillTree
2
+ module Controller
3
+ def self.included(base)
4
+ base.send :extend, ClassMethods
5
+ base.send :include, InstanceMethods
6
+ end
7
+
8
+ module InstanceMethods
9
+ def self.included(base)
10
+ base.send :before_filter, :check_allow_filter!
11
+ end
12
+
13
+ def can?(action, resource)
14
+ if current_user
15
+ current_user.can?(action, resource)
16
+ else
17
+ resource.default_permission?(action, :guest)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def check_allow_filter!
24
+ fail SkillTree::NotAuthorizedError unless can_access?
25
+ end
26
+
27
+ def can_access?
28
+ acl = current_acl[action_name.to_sym]
29
+ if acl.respond_to? :call
30
+ instance_eval(&acl)
31
+ elsif default_acl.respond_to? :call
32
+ instance_eval(&default_acl)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def default_acl
39
+ current_acl[:all]
40
+ end
41
+
42
+ def current_acl
43
+ self.class.current_acl
44
+ end
45
+ end
46
+
47
+ module ClassMethods
48
+ def current_acl
49
+ @current_acl ||= {}
50
+ end
51
+
52
+ def allow(*args, &block)
53
+ args.each { |key| current_acl[key] = block }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,5 @@
1
+ module SkillTree
2
+ class Error < ::StandardError; end
3
+ class AclAlreadySet < Error; end
4
+ class NotAuthorizedError < Error; end
5
+ end
@@ -0,0 +1,10 @@
1
+ module SkillTree
2
+ module Model
3
+ end
4
+ end
5
+ require 'skill_tree/models/acl.rb'
6
+ require 'skill_tree/models/acl_mapping.rb'
7
+ require 'skill_tree/models/acl_ownership.rb'
8
+ require 'skill_tree/models/permission.rb'
9
+ require 'skill_tree/models/role.rb'
10
+ require 'skill_tree/models/user_role.rb'
@@ -0,0 +1,13 @@
1
+ module SkillTree
2
+ module Models
3
+ class Acl < ActiveRecord::Base
4
+ has_many :acl_ownerships
5
+ has_many :acl_mappings
6
+
7
+ has_many :roles, -> { uniq }, through: :acl_mappings
8
+
9
+ validates :name, presence: true, uniqueness: true
10
+ validates :version, presence: true
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ module SkillTree
2
+ module Models
3
+ class AclMapping < ActiveRecord::Base
4
+ belongs_to :acl
5
+ belongs_to :role
6
+ belongs_to :permission
7
+
8
+ validates :acl, presence: true
9
+ validates :role, presence: true
10
+ validates :permission, presence: true
11
+ validates :acl_id, uniqueness: {
12
+ scope: [:permission_id, :role_id]
13
+ }
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ module SkillTree
2
+ module Models
3
+ class AclOwnership < ActiveRecord::Base
4
+ belongs_to :acl
5
+ belongs_to :resource, polymorphic: true
6
+
7
+ validates :acl, presence: true
8
+ validates :resource_id, uniqueness: { scope: [:resource_type] }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ module SkillTree
2
+ module Models
3
+ class Permission < ActiveRecord::Base
4
+ has_many :acl_mappings
5
+ validates :name, presence: true, uniqueness: true
6
+
7
+ scope :names, -> { pluck(:name).map(&:to_sym) }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,22 @@
1
+ module SkillTree
2
+ module Models
3
+ class Role < ActiveRecord::Base
4
+ has_many :user_roles
5
+ has_many :acl_mappings
6
+ validates :name, presence: true, uniqueness: true
7
+
8
+ def self.find_for(user, resource)
9
+ if user
10
+ user_role = user.user_roles.where(resource: resource).first
11
+ if user_role
12
+ user_role.role
13
+ else
14
+ find_by_name!('user')
15
+ end
16
+ else
17
+ find_by_name!('guest')
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ module SkillTree
2
+ module Models
3
+ class UserRole < ActiveRecord::Base
4
+ belongs_to :user
5
+ belongs_to :role
6
+ belongs_to :resource, polymorphic: true
7
+
8
+ validates :user_id, uniqueness: {
9
+ scope: [:resource_id, :resource_type, :role_id],
10
+ message: 'Duplicate role.'
11
+ }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,44 @@
1
+ require 'skill_tree/parser/acl_parser.rb'
2
+ require 'skill_tree/parser/role_parser.rb'
3
+
4
+ module SkillTree
5
+ module Parser
6
+ class Initializer
7
+ class <<self
8
+ def parse!
9
+ @parser = Initializer.new
10
+ @parser.setup if SkillTree::Models::Acl.table_exists?
11
+ end
12
+
13
+ def default_acl_for(model)
14
+ @parser.acls.each do |acl|
15
+ return acl.model if acl.default_for.to_s == model.class.table_name
16
+ end if @parser
17
+ nil
18
+ end
19
+ end
20
+
21
+ attr_reader :acls
22
+ def initialize
23
+ @acls = []
24
+ end
25
+
26
+ def setup(filename = 'config/acl.rb')
27
+ instance_eval(File.read(Rails.root.join(filename)))
28
+ save_db!
29
+ end
30
+
31
+ def acl(*args)
32
+ acl = AclParser.new(*args)
33
+ yield acl
34
+ @acls << acl
35
+ end
36
+
37
+ private
38
+
39
+ def save_db!
40
+ @acls.map(&:sync_model)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,42 @@
1
+ module SkillTree
2
+ module Parser
3
+ class AclParser
4
+ attr_reader :roles, :default_for
5
+ def initialize(name, options = {})
6
+ @name = name.to_s
7
+ @default_for = options[:default_for]
8
+ @roles = []
9
+ @version = options[:version]
10
+ end
11
+
12
+ def role(*args)
13
+ parser = RoleParser.new(*args)
14
+ parser.parent = self
15
+ yield parser
16
+ @roles << parser
17
+ end
18
+
19
+ def sync_model
20
+ acl = SkillTree::Models::Acl.find_or_initialize_by(name: @name)
21
+ return if version_match? acl
22
+ update_version acl
23
+ @roles.each { |role| role.sync_model acl }
24
+ acl.save!
25
+ end
26
+
27
+ def model
28
+ SkillTree::Models::Acl.find_by(name: @name)
29
+ end
30
+
31
+ private
32
+
33
+ def version_match?(acl)
34
+ !acl.new_record? && acl.version == @version
35
+ end
36
+
37
+ def update_version(acl)
38
+ acl.version = @version || 0
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,57 @@
1
+ module SkillTree
2
+ module Parser
3
+ class RoleParser
4
+ attr_reader :permissions, :name
5
+ attr_writer :parent
6
+ def initialize(name)
7
+ @name = name.to_s
8
+ @permissions = []
9
+ end
10
+
11
+ def can(*args)
12
+ @permissions += args
13
+ end
14
+
15
+ def inherit(name)
16
+ element = @parent.roles.select { |e| e.name == name.to_s }.first
17
+ fail "There is no #{name} role" if element.nil?
18
+ @permissions += element.permissions
19
+ end
20
+
21
+ def sync_model(acl)
22
+ role = SkillTree::Models::Role.find_or_initialize_by(name: @name)
23
+ role.save!
24
+ sync_permissions(acl, role)
25
+ end
26
+
27
+ private
28
+
29
+ def sync_permissions(acl, role)
30
+ permission_models = create_permissions!
31
+ permission_models.each do |permission|
32
+ SkillTree::Models::AclMapping.find_or_initialize_by(
33
+ role: role,
34
+ acl: acl,
35
+ permission: permission
36
+ ).save!
37
+ end
38
+ purge_old! acl, role, permission_models
39
+ end
40
+
41
+ def create_permissions!
42
+ @permissions.map do |name|
43
+ model = SkillTree::Models::Permission.find_or_initialize_by(name: name)
44
+ model.save!
45
+ model
46
+ end
47
+ end
48
+
49
+ def purge_old!(acl, role, permissions)
50
+ SkillTree::Models::AclMapping.where(
51
+ role: role,
52
+ acl: acl
53
+ ).where.not(permission: permissions).destroy_all
54
+ end
55
+ end
56
+ end
57
+ end