consent 1.0.1 → 2.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 925e2e71db7ce7b4c7104784bbd54cfeb8273b2502f0f73f599e64754d5a010a
4
- data.tar.gz: 256326fbd83020d39881b53eadc1b3b7cb9c5169bedee5aac5491661a0e3023b
3
+ metadata.gz: 95c988d7005c2d40bc6c83a00421e15079737a8759ae635764baf1008e0bd588
4
+ data.tar.gz: bdd9f0cc35741ecdf89bbc9b68de9819bcedc87b3e98ddc05a00d086c66d0b6d
5
5
  SHA512:
6
- metadata.gz: 84ee14b357d0edd8c7be190cc4f79e67d6a0a7f8e0f869d1bf15b0d1ebe87e6f076939aa9db684c172eec8f489bb83f8794c3ca0946a4edc569c07f04a6a9889
7
- data.tar.gz: 490da917b1363a0b5d2f209df9363978764874fbe74d500c548b91a95f16f3ca35fab2a80a1ab7ac4de7364e7892101efc4e4a022d25683d0dc82896c7b39da9
6
+ metadata.gz: '008f73e94d325ab83a1a63ffa696aea1e22c6db5b44ef49f9d11bc2c1d8773d5a9cf3eec7466870b4cfabb2d13ec23f4d6fa3742cfa5dd6c890322e83694fe0a'
7
+ data.tar.gz: aaa27addbb0c0372660f8680e981be5b48a5cc5bb30c42f22d0d75a347111f1733c5b410b67ca8f5e5259bdb76b9283f2af93ec5ce18350aeeed47ac48ccdc87
data/.gitignore CHANGED
@@ -1,10 +1,18 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
- /Gemfile.lock
4
3
  /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
4
+ coverage
5
+ pkg
8
6
  /spec/reports/
9
- /tmp/
10
- /consent-*.gem
7
+ **/tmp/*
8
+ !**/tmp/.gitkeep
9
+ !tmp/.gitignore
10
+ vendor/bundle
11
+ *.log
12
+ *.sqlite
13
+ *.sqlite3
14
+ Gemfile.lock
15
+
16
+ # Ignore uploaded files in development
17
+ /storage/*
18
+ !/storage/.keep
data/.rubocop.yml CHANGED
@@ -1 +1,10 @@
1
1
  inherit_from: .rubocop_todo.yml
2
+
3
+ require:
4
+ - rubocop-powerhome
5
+
6
+ AllCops:
7
+ TargetRubyVersion: 2.7
8
+
9
+ Rails:
10
+ Enabled: false
data/.rubocop_todo.yml CHANGED
@@ -1,21 +1,19 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2019-11-20 02:06:29 -0300 using RuboCop version 0.65.0.
3
+ # on 2022-08-23 19:11:09 UTC using RuboCop version 1.35.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 5
10
- # Configuration parameters: CountComments, ExcludedMethods.
11
- # ExcludedMethods: refine
12
- Metrics/BlockLength:
9
+ # Offense count: 3
10
+ # Configuration parameters: AllowComments, AllowEmptyLambdas.
11
+ Lint/EmptyBlock:
13
12
  Exclude:
14
- - 'spec/**/*'
15
- - 'lib/consent/rspec.rb'
13
+ - 'spec/consent_spec.rb'
16
14
 
17
- # Offense count: 9
18
- Style/Documentation:
15
+ # Offense count: 1
16
+ # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods.
17
+ Metrics/MethodLength:
19
18
  Exclude:
20
- - 'spec/**/*'
21
- - 'test/**/*'
19
+ - 'db/migrate/*.rb'
data/Gemfile CHANGED
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source 'https://rubygems.org'
3
+ source "https://rubygems.org"
4
4
 
5
5
  # Specify your gem's dependencies in consent.gemspec
6
6
  gemspec
7
+
8
+ rails_version = ENV.fetch("RAILS_VERSION", ">= 5")
9
+
10
+ gem "rails", rails_version
data/Rakefile CHANGED
@@ -1,8 +1,14 @@
1
+ #!/usr/bin/env rake
2
+
1
3
  # frozen_string_literal: true
2
4
 
3
- require 'bundler/gem_tasks'
4
- require 'rspec/core/rake_task'
5
+ require "bundler/setup"
6
+ Bundler::GemHelper.install_tasks
5
7
 
8
+ require "rspec/core/rake_task"
6
9
  RSpec::Core::RakeTask.new(:spec)
7
10
 
8
- task default: :spec
11
+ require "rubocop/rake_task"
12
+ RuboCop::RakeTask.new(:rubocop)
13
+
14
+ task default: %i[spec rubocop]
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Consent
4
+ module Authorizable
5
+ extend ::ActiveSupport::Concern
6
+
7
+ included do
8
+ has_many :permissions, class_name: "Consent::Permission",
9
+ foreign_key: :role_id, inverse_of: false,
10
+ dependent: :delete_all, autosave: true
11
+ end
12
+
13
+ # Grants all permissions in o permissions hash formatted as:
14
+ #
15
+ # `{ <subject> => { <action> => <view> } }`
16
+ #
17
+ # @example role.grant_all({ "user" => { "read" => "all" }})
18
+ # @example role.grant_all({ User => { read: :all }})
19
+ #
20
+ # When `replace: true`, it mark all existing permisions for destruction
21
+ #
22
+ # @example
23
+ # role.grant_all(User => { read: :territory })
24
+ # role.grant_all({ User => { write: :territory }, replace: true)
25
+ # role.permissions
26
+ # => [#<Consent::Permission subject: User(...), action: :write, view: :territory>]
27
+ #
28
+ # `grant_all` will only keep valid permissions, this excludes any permisison that grants nothing (:no_access)
29
+ #
30
+ # @param permissions [Hash] a hash formatted as documented above
31
+ # @param replace [Boolean] whether we should replace all existing granted permisions
32
+ #
33
+ def grant_all(permissions, replace: false)
34
+ changed = self.permissions
35
+ .from_hash(permissions)
36
+ .map { |permission| grant_permission(permission) }
37
+ (self.permissions - changed).each(&:mark_for_destruction) if replace
38
+ end
39
+
40
+ # Destructive form of {Authorizable#grant_all}. This methods grants all the given permissions and
41
+ # persists it to the database atomically
42
+ #
43
+ # @see #grant_all
44
+ # @yield after saving before commiting within the transaction
45
+ #
46
+ def grant_all!(*args, **kwargs)
47
+ transaction do
48
+ grant_all(*args, **kwargs)
49
+ tap(&:save!)
50
+ touch
51
+ yield if block_given?
52
+ end
53
+ end
54
+
55
+ # Grants a permission to a role, replacing any existing permission for the same subject/action pair:
56
+ #
57
+ # @example
58
+ # role.grant(subject: "user", action: "read", view: "all")
59
+ # role.grant(subject: "user", action: "read", view: "territory")
60
+ # role.permissions
61
+ # => [#<Consent::Permission subject: User(...), action: :read, view: :territory>]
62
+ #
63
+ # `grant` only grants valid permissions:
64
+ #
65
+ # @example
66
+ # role.grant(subject: "user", action: "read", view: "no_access")
67
+ # role.permissions
68
+ # => []
69
+ #
70
+ # `grant` also does not persist the given permissions, so the caller must #save! the role
71
+ #
72
+ # @param subject [Symbol|String|Class] any valid subject
73
+ # @param action [String|Symbol] a valid action
74
+ # @param view [String|Symbol] a valid view
75
+ #
76
+ def grant(subject:, action:, view:)
77
+ grant_permission ::Consent::Permission.new(subject: subject, action: action, view: view)
78
+ end
79
+
80
+ private
81
+
82
+ def grant_permission(new_perm)
83
+ existing_perm = permissions.find { |p| p.subject.eql?(new_perm.subject) && p.action.eql?(new_perm.action) }
84
+ if existing_perm
85
+ existing_perm.view = new_perm.view
86
+ existing_perm.mark_for_destruction unless existing_perm.valid?
87
+ existing_perm
88
+ elsif new_perm.valid?
89
+ association(:permissions).add_to_target(new_perm)
90
+ new_perm
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Consent
4
+ class ApplicationRecord < ActiveRecord::Base
5
+ self.abstract_class = true
6
+ end
7
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Consent
4
+ class History < ::Consent::ApplicationRecord
5
+ enum command: { grant: "grant", revoke: "revoke" }
6
+
7
+ serialize :subject, ::Consent::SubjectCoder
8
+ validates :subject, presence: true
9
+ validates :action, presence: true
10
+ validates :view, presence: true
11
+ validates :command, presence: true
12
+
13
+ def self.record(command, permission)
14
+ create!(
15
+ **permission.slice(:role_id, :subject, :action, :view),
16
+ command: command.to_s
17
+ )
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Consent
4
+ class Permission < ::Consent::ApplicationRecord
5
+ serialize :subject, ::Consent::SubjectCoder
6
+
7
+ validates :subject, presence: true
8
+ validates :action, presence: true
9
+ validates :view, presence: true,
10
+ exclusion: {
11
+ in: [::Consent::NO_ACCESS],
12
+ message: "must grant access",
13
+ }
14
+ after_save { ::Consent::History.record(:grant, self) }
15
+ after_destroy { ::Consent::History.record(:revoke, self) }
16
+
17
+ scope :to, ->(subject:, action: nil, view: nil) do
18
+ where({ subject: subject, action: action, view: view }.compact)
19
+ end
20
+
21
+ # It is true when it is a replacement for another permission
22
+ # @private
23
+ #
24
+ def replaces?(permission)
25
+ subject == permission.subject && action == permission.action
26
+ end
27
+
28
+ # Symbol key of an action
29
+ #
30
+ def action
31
+ super&.to_sym
32
+ end
33
+
34
+ # Symbol key of a view or "1" for full access
35
+ #
36
+ def view
37
+ return "1" if Consent::FULL_ACCESS.include?(super)
38
+
39
+ super&.to_sym
40
+ end
41
+
42
+ # Transforms a hash of permissions and views to grant into a collection
43
+ # of Consent::Permission
44
+ #
45
+ # I.e.:
46
+ # Permission.from_hash User => { write: :territory }
47
+ # => [#<Consent::Permission view: :territory, action: :write, subject: User>]
48
+ #
49
+ # Permission.from_hash User => { write: :territory, read: :all }
50
+ # => [#<Consent::Permission view: :territory, action: :write, subject: User>,]
51
+ # #<Consent::Permission view: :all, action: :read, subject: User>]
52
+ #
53
+ # It also eliminates any invalid permission from the resulting set
54
+ #
55
+ # I.e.:
56
+ # Permission.from_hash User => { write: :territory, read: :no_access }, Department: { write: :all }
57
+ # => [#<Consent::Permission view: :territory, action: :write, subject: User>,]
58
+ # #<Consent::Permission view: :all, action: :write, subject: Department>]
59
+ #
60
+ # @param permissions [Hash] a set of permissions in the hash format
61
+ # @return [Array<Consent::Permission>]
62
+ #
63
+ def self.from_hash(permissions)
64
+ permissions.flat_map do |subject, actions|
65
+ actions.flat_map do |action, view|
66
+ new(subject: subject, action: action, view: view)
67
+ end
68
+ end.select(&:valid?)
69
+ end
70
+ end
71
+ end
data/config.ru ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems"
4
+ require "bundler"
5
+
6
+ Bundler.require :default, :development
7
+
8
+ Combustion.initialize! :all
9
+ run Combustion::Application
data/consent.gemspec CHANGED
@@ -1,31 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- lib = File.expand_path('lib', __dir__)
3
+ lib = File.expand_path("lib", __dir__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require 'consent/version'
5
+ require "consent/version"
6
6
 
7
7
  Gem::Specification.new do |spec|
8
- spec.name = 'consent'
8
+ spec.name = "consent"
9
9
  spec.version = Consent::VERSION
10
- spec.authors = ['Carlos Palhares']
11
- spec.email = ['chjunior@gmail.com']
10
+ spec.authors = ["Carlos Palhares"]
11
+ spec.email = ["chjunior@gmail.com"]
12
12
 
13
- spec.summary = 'Consent'
14
- spec.description = 'Consent'
13
+ spec.summary = "Consent permission based authorization"
14
+ spec.description = "Consent permission based authorization"
15
+ spec.homepage = "https://github.com/powerhome/power-tools"
16
+ spec.license = "MIT"
17
+ spec.required_ruby_version = ">= 2.7"
15
18
 
16
- spec.licenses = ['MIT']
19
+ spec.files = `git ls-files`.split.grep_v(/^(test|spec|features)/)
20
+ spec.require_paths = ["lib"]
17
21
 
18
- spec.files = `git ls-files`.split.reject do |file|
19
- file =~ /^(test|spec|features)/
20
- end
21
- spec.require_paths = ['lib']
22
+ spec.add_dependency "cancancan", "3.2.1"
22
23
 
23
- spec.add_development_dependency 'activerecord', '>= 5'
24
- spec.add_development_dependency 'bundler', '>= 1.17.3'
25
- spec.add_development_dependency 'cancancan', '~> 1.15.0'
26
- spec.add_development_dependency 'pry', '~> 0.14.1'
27
- spec.add_development_dependency 'rake', '>= 12.3.3'
28
- spec.add_development_dependency 'rspec', '~> 3.0'
29
- spec.add_development_dependency 'rubocop', '~> 0.65.0'
30
- spec.add_development_dependency 'sqlite3', '~> 1.4.2'
24
+ spec.add_development_dependency "activerecord", ">= 5"
25
+ spec.add_development_dependency "bundler", "~> 2.1"
26
+ spec.add_development_dependency "combustion", "~> 1.3"
27
+ spec.add_development_dependency "license_finder", ">= 7.0"
28
+ spec.add_development_dependency "pry-byebug", "3.9.0"
29
+ spec.add_development_dependency "rake", "~> 13"
30
+ spec.add_development_dependency "rspec", "~> 3.0"
31
+ spec.add_development_dependency "rspec-rails", "~> 5.1.2"
32
+ spec.add_development_dependency "rubocop-powerhome", "0.5.0"
33
+ spec.add_development_dependency "sqlite3", "~> 1.4.2"
34
+ spec.metadata["rubygems_mfa_required"] = "true"
31
35
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateNitroAuthAuthorizationPermissions < ActiveRecord::Migration[5.2]
4
+ def change
5
+ create_table :"#{Consent.table_name_prefix}permissions" do |t|
6
+ t.string :subject, limit: 80
7
+ t.string :action, limit: 80
8
+ t.string :view, limit: 80
9
+ t.integer :role_id
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :"#{Consent.table_name_prefix}permissions", :role_id
15
+ add_index :"#{Consent.table_name_prefix}permissions", :subject
16
+ add_index :"#{Consent.table_name_prefix}permissions", %i[subject action],
17
+ name: :"idx_#{Consent.table_name_prefix}permissions_on_subject_and_action"
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateNitroAuthAuthorizationHistories < ActiveRecord::Migration[5.2]
4
+ def change
5
+ create_table :"#{Consent.table_name_prefix}histories" do |t|
6
+ t.string :command, limit: 6
7
+ t.string :subject, limit: 80
8
+ t.string :action, limit: 80
9
+ t.string :view, limit: 80
10
+ t.integer :role_id
11
+
12
+ t.timestamps
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ ---
2
+ - - :inherit_from
3
+ - https://raw.githubusercontent.com/powerhome/oss-guide/master/license_rules.yml
data/docs/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ ## [Unreleased]
2
+
3
+ ## [2.0.0] - 2022-09-02
4
+
5
+ - Support multiple versions of rails (>= 5.2.8.1)
6
+ - Consent is now an engine, and provides the ability to store and manage permissions for a Consent::Authorizable class (i.e.: Role)
7
+ - Consent::Ability is improved to support the new Permission model
8
+
9
+ ## [1.0.1] - 2021-05-10
10
+
11
+ - Improve Rspec matchers
12
+
13
+ ## [1.0.0] - 2021-04-22
14
+
15
+ - Consent released with DSL to design permissions and convenient CanCan::Ability implementation.
16
+
17
+ ## [0.6.0] - 2020-12-22
18
+ ## [0.5.2] - 2019-11-28
19
+ ## [0.5.0] - 2019-11-25
20
+ ## [0.4.3] - 2019-04-02
21
+ ## [0.4.2] - 2019-03-28
22
+ ## [0.4] - 2019-03-27
23
+ ## [0.3.1] - 2016-11-08