rbacan 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.
@@ -1,33 +1,79 @@
1
1
  require 'active_support'
2
2
 
3
3
  module Rbacan
4
- module Permittable
5
- extend ActiveSupport::Concern
6
-
7
- included do
8
-
9
- has_many :user_roles, class_name: Rbacan.user_role_class, dependent: :destroy
10
- has_many :roles, class_name: Rbacan.role_class, through: :user_roles
11
-
12
- def assign_role(role_name)
13
- assigned_role = Rbacan::Role.find_by_name(role_name)
14
- Rbacan::UserRole.create(user_id: self.id, role_id: assigned_role.id)
15
- end
16
-
17
- def remove_role(role_name)
18
- removed_role = Rbacan::Role.find_by_name(role_name)
19
- Rbacan::UserRole.where(user_id: self.id, role_id: removed_role.id).destroy_all
20
- end
21
-
22
- def can?(permission)
23
- @user_roles = self.roles
24
- user_permission = Rbacan::Permission.find_by_name(permission)
25
- if user_permission && @user_roles.joins(:role_permissions).where(role_permissions: {permission_id: user_permission.id}).count > 0
26
- return true
27
- else
28
- return false
29
- end
30
- end
31
- end
4
+ module Permittable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ has_many :user_roles,
9
+ class_name: Rbacan.user_role_class,
10
+ dependent: :destroy
11
+ accepts_nested_attributes_for :user_roles
12
+ has_many :roles,
13
+ class_name: Rbacan.role_class,
14
+ through: :user_roles
15
+
16
+ # Returns users that have the given role (by name).
17
+ scope :with_role, ->(role_name) {
18
+ joins(:roles).where(Rbacan.role_table => { name: role_name.to_s })
19
+ }
20
+
21
+ # Returns users that have the given permission via any of their roles.
22
+ scope :with_permission, ->(permission_name) {
23
+ joins(roles: { role_permissions: :permission })
24
+ .where(Rbacan.permission_table => { name: permission_name.to_s })
25
+ }
26
+
27
+ # Assigns a role to the user. Idempotent — safe to call multiple times.
28
+ # Fix: was find_or_initialize_by (never persisted); now find_or_create_by.
29
+ def assign_role(role_name)
30
+ assigned_role = Rbacan.role_class.constantize.find_by_name(role_name.to_s)
31
+ raise ArgumentError, "Role '#{role_name}' not found" unless assigned_role
32
+
33
+ self.user_roles.find_or_create_by(role_id: assigned_role.id)
34
+ end
35
+
36
+ # Removes a role from the user.
37
+ # Fix: was Rbacan::UserRole.where(user_id: ...) — now uses the scoped
38
+ # association so it respects the configured class and any FK setup.
39
+ def remove_role(role_name)
40
+ removed_role = Rbacan.role_class.constantize.find_by_name(role_name.to_s)
41
+ return unless removed_role
42
+
43
+ self.user_roles.where(role_id: removed_role.id).destroy_all
44
+ end
45
+
46
+ # Returns true if the user has the named permission via any of their roles.
47
+ # Fix: was two queries; now a single EXISTS query via association joins.
48
+ def can?(permission_name)
49
+ self.roles
50
+ .joins(role_permissions: :permission)
51
+ .where(Rbacan.permission_table => { name: permission_name.to_s })
52
+ .exists?
53
+ end
54
+
55
+ # Returns true if the user has the named role.
56
+ def has_role?(role_name)
57
+ self.roles.where(name: role_name.to_s).exists?
58
+ end
59
+
60
+ # Returns true if the user has ANY of the listed roles.
61
+ def has_any_role?(*role_names)
62
+ self.roles.where(name: role_names.map(&:to_s)).exists?
63
+ end
64
+
65
+ # Returns true if the user has ALL of the listed permissions.
66
+ # Uses a single query: counts distinct matching permissions and compares
67
+ # to the requested set size.
68
+ def can_all?(*permission_names)
69
+ names = permission_names.map(&:to_s)
70
+ self.roles
71
+ .joins(role_permissions: :permission)
72
+ .where(Rbacan.permission_table => { name: names })
73
+ .select("#{Rbacan.permission_table}.name")
74
+ .distinct
75
+ .count == names.uniq.size
76
+ end
32
77
  end
78
+ end
33
79
  end
@@ -1,26 +1,34 @@
1
1
  module Rbacan
2
- module RolesAndPermissions
2
+ module RolesAndPermissions
3
3
 
4
- attr_reader :test_method
5
-
6
- def self.create_roles(roles)
7
- roles.each do |role|
8
- Rbacan::Role.create(name: role)
9
- end
10
- end
4
+ # Creates roles by name. Idempotent — skips existing ones.
5
+ def self.create_roles(roles)
6
+ roles.each do |role|
7
+ Rbacan.role_class.constantize.find_or_create_by(name: role.to_s)
8
+ end
9
+ end
10
+
11
+ # Creates permissions by name. Idempotent — skips existing ones.
12
+ def self.create_permissions(permissions)
13
+ permissions.each do |permission|
14
+ Rbacan.permission_class.constantize.find_or_create_by(name: permission.to_s)
15
+ end
16
+ end
17
+
18
+ # Assigns a list of permissions to a role. Idempotent — skips duplicates.
19
+ def self.assign_permissions_to_role(role_name, permissions)
20
+ chosen_role = Rbacan.role_class.constantize.find_by_name(role_name.to_s)
21
+ return unless chosen_role
11
22
 
12
- def self.create_permissions(permissions)
13
- permissions.each do |permission|
14
- Rbacan::Permission.create(name: permission)
15
- end
16
- end
23
+ permissions.each do |permission|
24
+ given_permission = Rbacan.permission_class.constantize.find_by_name(permission.to_s)
25
+ next unless given_permission
17
26
 
18
- def self.assign_permissions_to_role(role_name, permissions)
19
- chosen_role = Rbacan::Role.find_by_name(role_name)
20
- permissions.each do |permission|
21
- given_permission = Rbacan::Permission.find_by_name(permission)
22
- Rbacan::RolePermission.create(role_id: chosen_role.id, permission_id: given_permission.id)
23
- end
24
- end
27
+ Rbacan.role_permission_class.constantize.find_or_create_by(
28
+ role_id: chosen_role.id,
29
+ permission_id: given_permission.id
30
+ )
31
+ end
25
32
  end
33
+ end
26
34
  end
@@ -0,0 +1,50 @@
1
+ module Rbacan
2
+ class RouteConstraint
3
+ # Constrains routes to users that have a given role or permission.
4
+ #
5
+ # Usage in config/routes.rb:
6
+ # constraints Rbacan::RouteConstraint.new(role: :admin) do
7
+ # namespace :admin do
8
+ # resources :users
9
+ # end
10
+ # end
11
+ #
12
+ # constraints Rbacan::RouteConstraint.new(permission: :access_admin_panel) do
13
+ # ...
14
+ # end
15
+ def initialize(role: nil, permission: nil)
16
+ if role.nil? && permission.nil?
17
+ raise ArgumentError, "Provide exactly one of role: or permission:"
18
+ end
19
+ if role && permission
20
+ raise ArgumentError, "Provide exactly one of role: or permission:, not both"
21
+ end
22
+
23
+ @role = role&.to_s
24
+ @permission = permission&.to_s
25
+ end
26
+
27
+ def matches?(request)
28
+ user = current_user_from(request)
29
+ return false unless user
30
+
31
+ @role ? user.has_role?(@role) : user.can?(@permission)
32
+ end
33
+
34
+ private
35
+
36
+ def current_user_from(request)
37
+ # Warden (Devise) path — most common, no extra query needed.
38
+ warden = request.env['warden']
39
+ return warden.user if warden&.authenticated?
40
+
41
+ # Fallback: manual session lookup for apps not using Warden.
42
+ user_id = request.session[:user_id] || request.session['user_id']
43
+ return nil unless user_id
44
+
45
+ Rbacan.permittable_class.constantize.find_by(id: user_id)
46
+ rescue StandardError
47
+ nil
48
+ end
49
+ end
50
+ end
@@ -1,3 +1,3 @@
1
1
  module Rbacan
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,14 @@
1
+ module Rbacan
2
+ module ViewHelpers
3
+ # Renders the block only if the current user has the given permission.
4
+ #
5
+ # Usage in views:
6
+ # <% authorized?(:publish_post) do %>
7
+ # <%= link_to "Publish", publish_post_path(@post) %>
8
+ # <% end %>
9
+ def authorized?(permission, &block)
10
+ return unless current_user&.can?(permission.to_s)
11
+ capture(&block) if block_given?
12
+ end
13
+ end
14
+ end
data/lib/rbacan.rb CHANGED
@@ -1,7 +1,11 @@
1
1
  require "rbacan/version"
2
+ require "rbacan/not_authorized"
2
3
  require "rbacan/permittable"
3
- require 'rbacan/engine'
4
+ require "rbacan/engine"
4
5
  require "rbacan/roles_and_permissions"
6
+ require "rbacan/authorization"
7
+ require "rbacan/view_helpers"
8
+ require "rbacan/route_constraint"
5
9
 
6
10
  module Rbacan
7
11
  mattr_accessor :permittable_class
@@ -27,28 +31,29 @@ module Rbacan
27
31
  @@role_permission_class = 'Rbacan::RolePermission'
28
32
  @@role_permission_table = 'role_permissions'
29
33
 
30
- def create_role(role_name)
31
- @@role_class.create(name: role_name)
32
- end
34
+ # :raise (default), :redirect, or any callable (lambda/proc).
35
+ mattr_accessor :unauthorized_handler
36
+ @@unauthorized_handler = :raise
33
37
 
34
- def create_permission(permission_name)
35
- @@permission_class.create(name: permission_name)
36
- end
38
+ # Redirect path used when unauthorized_handler is :redirect.
39
+ mattr_accessor :unauthorized_redirect_path
40
+ @@unauthorized_redirect_path = '/'
37
41
 
38
- def assign_permission_to_role(role_name, permission_name)
39
- chosen_role = @@role_class.find_by_name(role_name)
40
- given_permission = @@permission_class.find_by_name(permission_name)
41
- @@role_permission_class.create(role_id: chosen_role.id, perm_id: given_permission.id)
42
+ def self.create_role(role_name)
43
+ @@role_class.constantize.create(name: role_name)
42
44
  end
43
45
 
44
- def assign_role(role_name)
45
- assigned_role = Role.find_by_name(role_name)
46
- @@user_role_class.create(user_id: self.id, role_id: assigned_role.id)
46
+ def self.create_permission(permission_name)
47
+ @@permission_class.constantize.create(name: permission_name)
47
48
  end
48
49
 
49
- def remove_user_role(role_name)
50
- removed_role = Role.find_by_name(role_name)
51
- @@user_role_class.where(user_id: self.id, role_id: removed_role.id).destroy_all
50
+ def self.assign_permission_to_role(role_name, permission_name)
51
+ chosen_role = @@role_class.constantize.find_by_name(role_name)
52
+ given_permission = @@permission_class.constantize.find_by_name(permission_name)
53
+ @@role_permission_class.constantize.create(
54
+ role_id: chosen_role.id,
55
+ permission_id: given_permission.id
56
+ )
52
57
  end
53
58
 
54
59
  def self.configure(&block)
@@ -56,5 +61,4 @@ module Rbacan
56
61
  end
57
62
 
58
63
  class Error < StandardError; end
59
- # Your code goes here...
60
64
  end
data/rbacan.gemspec CHANGED
@@ -9,26 +9,20 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["hamdi"]
10
10
  spec.email = ["hamdi_amiche@outlook.fr"]
11
11
 
12
- spec.summary = %q{a gem to give permission access to users looknig back to their roles}
13
- spec.description = %q{RBACan is a Role-based access control tool to control user access to the functionnalities of your application}
12
+ spec.summary = %q{A gem to give permission access to users based on their roles}
13
+ spec.description = %q{RBACan is a Role-Based Access Control tool to control user access to the functionalities of your application}
14
14
  spec.homepage = "https://github.com/hamdi777/RBACan"
15
- spec.license = "MIT"
15
+ spec.licenses = ["MIT"]
16
16
 
17
- # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
- # to allow pushing to a single host or delete this section to allow pushing to any host.
19
17
  if spec.respond_to?(:metadata)
20
- spec.metadata["allowed_push_host"] = "https://rubygems.org/profiles/hamdi777"
21
-
22
- spec.metadata["homepage_uri"] = spec.homepage
23
- spec.metadata["source_code_uri"] = "https://rubygems.org/profiles/hamdi777"
24
- spec.metadata["changelog_uri"] = "https://rubygems.org/profiles/hamdi777/CHANGELOG.md"
18
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/hamdi777/RBACan"
21
+ spec.metadata["changelog_uri"] = "https://github.com/hamdi777/RBACan/blob/master/CHANGELOG.md"
25
22
  else
26
- raise "RubyGems 2.0 or newer is required to protect against " \
27
- "public gem pushes."
23
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
28
24
  end
29
25
 
30
- # Specify which files should be added to the gem when it is released.
31
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
32
26
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
33
27
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
34
28
  end
@@ -36,10 +30,13 @@ Gem::Specification.new do |spec|
36
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
37
31
  spec.require_paths = ["lib"]
38
32
 
39
- spec.add_dependency 'rails', '>= 4.2'
40
- spec.add_development_dependency "bundler", "~> 2.0"
41
- spec.add_development_dependency "rake", "~> 10.0"
42
- spec.add_development_dependency "rspec", "~> 3.0"
43
- spec.add_development_dependency 'generator_spec', '~> 0.9.4'
44
- spec.add_development_dependency 'railties', '~> 5.2', '>= 5.2.3'
33
+ spec.add_dependency 'rails', '>= 5.2'
34
+
35
+ spec.add_development_dependency "bundler", ">= 2.0"
36
+ spec.add_development_dependency "rake", "~> 13.0"
37
+ spec.add_development_dependency "rspec", "~> 3.0"
38
+ spec.add_development_dependency "generator_spec", "~> 0.9.4"
39
+ spec.add_development_dependency "railties", ">= 5.2"
40
+ spec.add_development_dependency "activerecord", ">= 5.2"
41
+ spec.add_development_dependency "sqlite3", ">= 1.4"
45
42
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbacan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - hamdi
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2019-08-26 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rails
@@ -16,26 +15,26 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: '4.2'
18
+ version: '5.2'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
- version: '4.2'
25
+ version: '5.2'
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: bundler
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
- - - "~>"
30
+ - - ">="
32
31
  - !ruby/object:Gem::Version
33
32
  version: '2.0'
34
33
  type: :development
35
34
  prerelease: false
36
35
  version_requirements: !ruby/object:Gem::Requirement
37
36
  requirements:
38
- - - "~>"
37
+ - - ">="
39
38
  - !ruby/object:Gem::Version
40
39
  version: '2.0'
41
40
  - !ruby/object:Gem::Dependency
@@ -44,14 +43,14 @@ dependencies:
44
43
  requirements:
45
44
  - - "~>"
46
45
  - !ruby/object:Gem::Version
47
- version: '10.0'
46
+ version: '13.0'
48
47
  type: :development
49
48
  prerelease: false
50
49
  version_requirements: !ruby/object:Gem::Requirement
51
50
  requirements:
52
51
  - - "~>"
53
52
  - !ruby/object:Gem::Version
54
- version: '10.0'
53
+ version: '13.0'
55
54
  - !ruby/object:Gem::Dependency
56
55
  name: rspec
57
56
  requirement: !ruby/object:Gem::Requirement
@@ -84,24 +83,46 @@ dependencies:
84
83
  name: railties
85
84
  requirement: !ruby/object:Gem::Requirement
86
85
  requirements:
87
- - - "~>"
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '5.2'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
88
94
  - !ruby/object:Gem::Version
89
95
  version: '5.2'
96
+ - !ruby/object:Gem::Dependency
97
+ name: activerecord
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
90
100
  - - ">="
91
101
  - !ruby/object:Gem::Version
92
- version: 5.2.3
102
+ version: '5.2'
93
103
  type: :development
94
104
  prerelease: false
95
105
  version_requirements: !ruby/object:Gem::Requirement
96
106
  requirements:
97
- - - "~>"
107
+ - - ">="
98
108
  - !ruby/object:Gem::Version
99
109
  version: '5.2'
110
+ - !ruby/object:Gem::Dependency
111
+ name: sqlite3
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '1.4'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
100
121
  - - ">="
101
122
  - !ruby/object:Gem::Version
102
- version: 5.2.3
103
- description: RBACan is a Role-based access control tool to control user access to
104
- the functionnalities of your application
123
+ version: '1.4'
124
+ description: RBACan is a Role-Based Access Control tool to control user access to
125
+ the functionalities of your application
105
126
  email:
106
127
  - hamdi_amiche@outlook.fr
107
128
  executables: []
@@ -111,6 +132,7 @@ files:
111
132
  - ".gitignore"
112
133
  - ".rspec"
113
134
  - ".travis.yml"
135
+ - CHANGELOG.md
114
136
  - CODE_OF_CONDUCT.md
115
137
  - Gemfile
116
138
  - Gemfile.lock
@@ -132,20 +154,23 @@ files:
132
154
  - lib/generators/install/templates/rbacan.rb
133
155
  - lib/generators/rbacan/install_generator.rb
134
156
  - lib/rbacan.rb
157
+ - lib/rbacan/authorization.rb
135
158
  - lib/rbacan/engine.rb
159
+ - lib/rbacan/not_authorized.rb
136
160
  - lib/rbacan/permittable.rb
137
161
  - lib/rbacan/roles_and_permissions.rb
162
+ - lib/rbacan/route_constraint.rb
138
163
  - lib/rbacan/version.rb
164
+ - lib/rbacan/view_helpers.rb
139
165
  - rbacan.gemspec
140
166
  homepage: https://github.com/hamdi777/RBACan
141
167
  licenses:
142
168
  - MIT
143
169
  metadata:
144
- allowed_push_host: https://rubygems.org/profiles/hamdi777
170
+ allowed_push_host: https://rubygems.org
145
171
  homepage_uri: https://github.com/hamdi777/RBACan
146
- source_code_uri: https://rubygems.org/profiles/hamdi777
147
- changelog_uri: https://rubygems.org/profiles/hamdi777/CHANGELOG.md
148
- post_install_message:
172
+ source_code_uri: https://github.com/hamdi777/RBACan
173
+ changelog_uri: https://github.com/hamdi777/RBACan/blob/master/CHANGELOG.md
149
174
  rdoc_options: []
150
175
  require_paths:
151
176
  - lib
@@ -160,9 +185,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
160
185
  - !ruby/object:Gem::Version
161
186
  version: '0'
162
187
  requirements: []
163
- rubyforge_project:
164
- rubygems_version: 2.6.13
165
- signing_key:
188
+ rubygems_version: 3.6.8
166
189
  specification_version: 4
167
- summary: a gem to give permission access to users looknig back to their roles
190
+ summary: A gem to give permission access to users based on their roles
168
191
  test_files: []