permits 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '081bbbbc6f1e3880c891c01fb9f5a046abb33001d2435ee81075ec7b8144c578'
4
+ data.tar.gz: 48388184153b9ca8f289dd601f4fcef2f1bbc0de669c8a8a28e7f5a47bfca432
5
+ SHA512:
6
+ metadata.gz: 9bf6cf28caa90ede7b4ccd5fcfbd55f05a466bdb1ebdda4a38d0ce19a00ce046791ff6f81ae9889277abedff29575afc97552175c0e55a513acb8e31bdf20707
7
+ data.tar.gz: 6381568c986046227947542c0ad02ea305a325198164c861aae46f5f24ad1735de11b38c2bdf7347e095feeea27d4717d6d67e27658528ce2a8c364c5229b73c
data/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # Permits
2
+ `Permits` offers an ActiveRecord-based permissions system for Rails applications, with historic records (via `Timelines::Ephemeral`) and a simple DSL for defining permissions and policies.
3
+
4
+ ## Usage
5
+ ### Initialization
6
+ Set up the valid `permits` values (roles/actions/levels):
7
+ ```ruby
8
+ # config/initializers/permits.rb
9
+
10
+ # user levels example
11
+ ::Permits.configure do |config|
12
+ config.permits = %i[admin user super_user]
13
+ end
14
+
15
+ # user actions example
16
+ ::Permits.configure do |config|
17
+ config.actions = %i[read write destroy]
18
+ end
19
+
20
+ # mixed example
21
+ ::Permits.configure do |config|
22
+ config.roles = %i[admin super_user read write destroy]
23
+ end
24
+ ```
25
+
26
+ ### Creating Permission Record
27
+ Create a `Permission` record to track an `Owner's` access to a selected `Resource`, and define the action/level that the `Owner` is permitted to work with the `Resource`:
28
+
29
+ Give a `User` permission to do `write` to a certain `Resource`:
30
+ ```ruby
31
+ Permits::Permission.create(owner: owner, resource: resource, action: :write)
32
+ ```
33
+
34
+ ### Checking Permissions with the default Policy
35
+ The `Permits::Policy::Base` class provides two different class methods for authorizing access:
36
+ - `Permits::Policy::Base.authorize!` will raise an `Permits::Policy::UnauthorizedError` if the `Owner` does not have the required permission
37
+ - `Permits::Policy::Base.authorized?` will return `true` if the `Owner` has the required permission, and `false` if not
38
+
39
+
40
+ ```ruby
41
+ Permits::Permission.create(owner: owner, resource: resource, action: :write)
42
+
43
+ Permits::Policy::Base.authorize!(owner, resource, :write) # => true
44
+ Permits::Policy::Base.authorize!(owner, resource, :read) # => Permits::Policy::UnauthorizedError
45
+ Permits::Policy::Base.authorize!(owner, other_resource, :write) # => Permits::Policy::UnauthorizedError
46
+
47
+ Permits::Policy::Base.authorized?(owner, resource, :write) # => true
48
+ Permits::Policy::Base.authorized?(owner, resource, :read) # => false
49
+ Permits::Policy::Base.authorized?(owner, resource, :write) # => false
50
+ ```
51
+
52
+ ### Custom Policies
53
+ You can define custom policies by subclassing `Permits::Policy::Base` and defining an `#{action_name}?` method. Furthermore you can still leverage the `has_action_permissions?` method from the `Base` class to perform the check that they have a suitable `Permission` record while also making additional checks (such as checking they are an Admin, or checking that a resource is active/actionable)
54
+
55
+ ```ruby
56
+ # config/initializers/permits.rb
57
+ ::Permits.configure do |config|
58
+ config.roles = %i[some_action]
59
+ end
60
+
61
+ # app/models/custom_policy.rb
62
+ class CustomPolicy < Permits::Policy::Base
63
+ def some_action?
64
+ owner == resource.owner || owner.admin?
65
+ end
66
+
67
+ def some_action_with_level?
68
+ owner.admin? && has_action_permissions?(:some_action)
69
+ end
70
+ end
71
+
72
+ # irb
73
+ Permits::Permission.create(owner: owner, resource: resource, action: :some_action)
74
+
75
+ CustomPolicy.authorize!(owner, resource, :some_action)
76
+ CustomPolicy.authorized?(owner, resource, :some_action)
77
+ ```
78
+
79
+ ## Installation
80
+ Add this line to your application's Gemfile:
81
+
82
+ ```ruby
83
+ gem "permits"
84
+ ```
85
+
86
+ And then execute:
87
+ ```bash
88
+ $ bundle
89
+ ```
90
+
91
+ Or install it yourself as:
92
+ ```bash
93
+ $ gem install permits
94
+ ```
95
+
96
+ Install the required files to your project:
97
+ ```bash
98
+ $ rails generate permits:install
99
+ ```
100
+
101
+ ## Contributing
102
+ Contribution directions go here.
103
+
104
+ ## License
105
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ Bundler::GemHelper.install_tasks
6
+ rescue LoadError
7
+ puts 'although not required, bundler is recommended for running the tests'
8
+ end
9
+
10
+ task default: :spec
11
+
12
+ require 'rspec/core/rake_task'
13
+ RSpec::Core::RakeTask.new(:spec)
14
+
15
+ require 'rubocop/rake_task'
16
+ RuboCop::RakeTask.new do |task|
17
+ task.requires << 'rubocop-performance'
18
+ task.requires << 'rubocop-rspec'
19
+ end
@@ -0,0 +1,2 @@
1
+ Description:
2
+ Copies the Database migration file to your application's db/migrate directory.
@@ -0,0 +1,11 @@
1
+ module Permits
2
+ module Generators
3
+ class InstallGenerator < ::Rails::Generators::Base
4
+ source_root File.expand_path("templates", __dir__)
5
+
6
+ def copy_application_policy
7
+ template "create_permits_permissions.rb", "db/migrate/#{Time.current.strftime("%Y%m%d%H%M%S")}_create_permits_permissions.rb"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ class CreatePermitPermissions < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table :permits_permissions, id: :uuid do |t|
4
+ t.string :owner_id, null: false
5
+ t.string :owner_type, null: false
6
+ t.string :resource_id, null: false
7
+ t.string :resource_type, null: false
8
+ t.string :permits, null: false
9
+
10
+ t.datetime :started_at
11
+ t.datetime :ended_at
12
+ t.timestamps
13
+ end
14
+
15
+ add_index :permits_permissions, [:owner_id, :owner_type]
16
+ add_index :permits_permissions, [:resource_id, :resource_type]
17
+ end
18
+ end
@@ -0,0 +1,25 @@
1
+ require "timelines"
2
+
3
+ module Permits
4
+ class Permission < ActiveRecord::Base
5
+ self.table_name = "permits_permissions"
6
+
7
+ include ::Timelines::Ephemeral
8
+ attribute :started_at, default: -> { Time.current }
9
+
10
+ belongs_to :owner, polymorphic: true, required: true
11
+ belongs_to :resource, polymorphic: true, required: true
12
+
13
+ validates :owner, presence: true
14
+ validates :resource, presence: true
15
+ validate :permits_is_permissable
16
+
17
+ def permits_is_permissable
18
+ return if Permits.config.permits&.include?(permits&.to_sym)
19
+
20
+ errors.add(:permits, "is not a valid permits for #{resource.class}")
21
+ end
22
+
23
+ scope :permits_any, -> { all }
24
+ end
25
+ end
@@ -0,0 +1,51 @@
1
+ module Permits
2
+ module Policy
3
+ class Base
4
+ include ActiveModel::Model
5
+ include ActiveModel::Attributes
6
+ include ActiveModel::Validations
7
+
8
+ attribute :owner
9
+ attribute :resource
10
+
11
+ validates :owner, presence: true
12
+ validates :resource, presence: true
13
+
14
+ def self.authorize!(owner, resource, action)
15
+ policy = new(owner: owner, resource: resource)
16
+ if policy.authorized?(action)
17
+ true
18
+ else
19
+ raise ::Permits::Policy::UnauthorizedError
20
+ end
21
+ end
22
+
23
+ def self.authorized?(owner, resource, action)
24
+ policy = new(owner: owner, resource: resource)
25
+ policy.authorized?(action)
26
+ end
27
+
28
+ def authorized?(action)
29
+ return false unless valid?
30
+
31
+ if respond_to?("#{action}?")
32
+ return send("#{action}?")
33
+ else
34
+ has_action_permissions?(action)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def has_action_permissions?(action)
41
+ return false unless owner_permissions.respond_to?("permits_#{action}")
42
+
43
+ return owner_permissions.send("permits_#{action}").where(resource: resource).exists?
44
+ end
45
+
46
+ def owner_permissions
47
+ @owner_permissions ||= ::Permits::Permission.active.where(owner: owner).includes(:resource)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,6 @@
1
+ module Permits
2
+ module Policy
3
+ class UnauthorizedError < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,4 @@
1
+ module Permits
2
+ class Railtie < ::Rails::Railtie
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module Permits
2
+ VERSION = "0.1.0"
3
+ end
data/lib/permits.rb ADDED
@@ -0,0 +1,23 @@
1
+ require "permits/version"
2
+ require "permits/railtie"
3
+ require "permits/permission"
4
+ require "permits/policy/base"
5
+ require "permits/policy/unauthorized_error"
6
+ require "active_support/configurable"
7
+
8
+ module Permits
9
+ include ActiveSupport::Configurable
10
+
11
+ class << self
12
+ def configure
13
+ yield config
14
+
15
+ if config.permits
16
+ config.permits = config.permits.map!(&:to_sym)
17
+ config.permits.each do |role|
18
+ ::Permits::Permission.scope "permits_#{role}", -> { where(permits: role) }
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :permits do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: permits
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Craig Gilchrist
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-10-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '7.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '7.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: timelines
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dotenv
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.8'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '7.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '7.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '13.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.4'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.4'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '4.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '4.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.21'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.21'
125
+ description: Provides access authorization for owners and resources, via a simple
126
+ Permission ActiveRecord model and a Policy class.
127
+ email:
128
+ - craig.a.gilchrist@gmail.com
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - README.md
134
+ - Rakefile
135
+ - lib/generators/permits/install/USAGE
136
+ - lib/generators/permits/install/install_generator.rb
137
+ - lib/generators/permits/install/templates/create_permits_permissions.rb
138
+ - lib/permits.rb
139
+ - lib/permits/permission.rb
140
+ - lib/permits/policy/base.rb
141
+ - lib/permits/policy/unauthorized_error.rb
142
+ - lib/permits/railtie.rb
143
+ - lib/permits/version.rb
144
+ - lib/tasks/permits_tasks.rake
145
+ homepage: https://github.com/Craggar/permits
146
+ licenses:
147
+ - MIT
148
+ metadata:
149
+ homepage_uri: https://github.com/Craggar/permits
150
+ source_code_uri: https://github.com/Craggar/permits
151
+ changelog_uri: https://github.com/Craggar/permits/CHANGELOG.md
152
+ post_install_message:
153
+ rdoc_options: []
154
+ require_paths:
155
+ - lib
156
+ required_ruby_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ required_rubygems_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ requirements: []
167
+ rubygems_version: 3.4.10
168
+ signing_key:
169
+ specification_version: 4
170
+ summary: A User and Role management gem for Rails 7
171
+ test_files: []