skill_tree 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|