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.
- checksums.yaml +7 -0
- data/lib/generators/skill_tree/install_generator.rb +20 -0
- data/lib/generators/skill_tree/orm_helpers.rb +14 -0
- data/lib/generators/skill_tree/templates/acl.rb +42 -0
- data/lib/generators/skill_tree/templates/create_skill_tree_acl_system.rb +48 -0
- data/lib/generators/skill_tree/templates/initializer.rb +1 -0
- data/lib/skill_tree.rb +15 -0
- data/lib/skill_tree/controller.rb +57 -0
- data/lib/skill_tree/errors.rb +5 -0
- data/lib/skill_tree/models.rb +10 -0
- data/lib/skill_tree/models/acl.rb +13 -0
- data/lib/skill_tree/models/acl_mapping.rb +16 -0
- data/lib/skill_tree/models/acl_ownership.rb +11 -0
- data/lib/skill_tree/models/permission.rb +10 -0
- data/lib/skill_tree/models/role.rb +22 -0
- data/lib/skill_tree/models/user_role.rb +14 -0
- data/lib/skill_tree/parser.rb +44 -0
- data/lib/skill_tree/parser/acl_parser.rb +42 -0
- data/lib/skill_tree/parser/role_parser.rb +57 -0
- data/lib/skill_tree/resource.rb +21 -0
- data/lib/skill_tree/resource/callbacks.rb +20 -0
- data/lib/skill_tree/resource/class_methods.rb +10 -0
- data/lib/skill_tree/resource/instance_methods.rb +52 -0
- data/lib/skill_tree/resource/relations.rb +18 -0
- data/lib/skill_tree/resource/scopes.rb +38 -0
- data/lib/skill_tree/subject.rb +53 -0
- data/lib/skill_tree/version.rb +3 -0
- data/spec/db/schema.rb +59 -0
- data/spec/skill_tree/controller_spec.rb +103 -0
- data/spec/skill_tree/generators/install_generator_spec.rb +34 -0
- data/spec/skill_tree/models/acl_mapping_spec.rb +46 -0
- data/spec/skill_tree/models/acl_ownership_spec.rb +30 -0
- data/spec/skill_tree/models/acl_spec.rb +32 -0
- data/spec/skill_tree/models/permission_spec.rb +16 -0
- data/spec/skill_tree/models/role_spec.rb +41 -0
- data/spec/skill_tree/models/user_role_spec.rb +16 -0
- data/spec/skill_tree/parser_spec.rb +71 -0
- data/spec/skill_tree/resource_spec.rb +137 -0
- data/spec/skill_tree/subject_spec.rb +65 -0
- data/spec/skill_tree_spec.rb +8 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/support/acls/acl_example.rb +19 -0
- data/spec/support/acls/acl_example2.rb +24 -0
- data/spec/support/acls/acl_example3.rb +24 -0
- data/spec/support/acls/acl_example4.rb +24 -0
- data/spec/support/acls/subject_acl.rb +57 -0
- data/spec/support/capture_stdout.rb +12 -0
- data/spec/support/controllers.rb +71 -0
- data/spec/support/model_builder.rb +15 -0
- data/spec/support/models.rb +16 -0
- metadata +232 -0
checksums.yaml
ADDED
|
@@ -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!
|
data/lib/skill_tree.rb
ADDED
|
@@ -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,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,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
|