consent 1.0.1 → 2.0.0

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