skill_tree 0.0.1

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 (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