permits 0.1.4 → 1.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: 81ac6b69542c7d7b1f09688cbe4beb0896990b297c6057f2c714eccf479d1682
4
- data.tar.gz: d3a0f82e5a9d7d2167a33f945b1cd6017451aea44912826c35da02aa84f01055
3
+ metadata.gz: 17d6289fc58da4116cf85b5cf7853ebfe67d6366a4e9c96b64b0d0d42533d1a4
4
+ data.tar.gz: '08f713827ae201b471163040ce273e2b7f5beb5e59f31c9af0d808e3d1248f7e'
5
5
  SHA512:
6
- metadata.gz: 5e8cd3716c4741fa257cdee4bb9f5e205c7706266bc4d0cb9414223923e799daa14b730e62908f0d855dcc19727e3bf1ce27656c609bf562aac74059b1373e13
7
- data.tar.gz: 98450a579ce833d512cd4b358c5f311e697c99d770471408297aed2892380d54c06374f4514834009d4b2f62243dd2512f73a4a3724e0bb1aed9cad22e916db6
6
+ metadata.gz: 45c24dc5c7c076e30644f57a9779d50d252551921fa3e7eb7de6d21fe588f5e5217c5f90638cf98bc570ec9daf75526f910f0f015bf4e72ec923e860f0ecb420
7
+ data.tar.gz: 4bec82908bb725ea9a36fac20d0d1ee091923509b2f3a52b2ce59649fb011a586e67e70c7d93e3ad1ce480f496432af84354787ea285753a75bc8eda67d3b8a3
data/README.md CHANGED
@@ -76,6 +76,23 @@ CustomPolicy.authorize!(owner, resource, :some_action)
76
76
  CustomPolicy.authorized?(owner, resource, :some_action)
77
77
  ```
78
78
 
79
+ ### Invites
80
+ `Permits` provides a simple pre-permissioned Invite system, with a `Permits::Invite` model, a `Permits::Form::NewInviteForm` form object for creating new invites, and a `Permits::Form::InviteForm` form object for accepting, declining and revoking invites. Invites can be pre-assigned permissions, so that when the invite is accepted, the invitee is automatically granted the permissions.
81
+
82
+ ```irb
83
+ group = Group.create(name: "Group 1")
84
+ group_user = User.create(name: "User 1")
85
+ user_permission = Permits::Permission.create(owner: group_user, resource: group, action: :super_user)
86
+
87
+ NewInviteForm.new(
88
+ invited_by: group_user,
89
+ email: "invitee@email-address.com",
90
+ permission_attributes: {
91
+ group => [:read, :edit]
92
+ }
93
+ ).save!
94
+ ```
95
+
79
96
  ## Installation
80
97
  Add this line to your application's Gemfile:
81
98
 
data/Rakefile CHANGED
@@ -1,19 +1,19 @@
1
- require 'rake'
1
+ require "rake"
2
2
 
3
3
  begin
4
- require 'bundler/setup'
4
+ require "bundler/setup"
5
5
  Bundler::GemHelper.install_tasks
6
6
  rescue LoadError
7
- puts 'although not required, bundler is recommended for running the tests'
7
+ puts "although not required, bundler is recommended for running the tests"
8
8
  end
9
9
 
10
10
  task default: :spec
11
11
 
12
- require 'rspec/core/rake_task'
12
+ require "rspec/core/rake_task"
13
13
  RSpec::Core::RakeTask.new(:spec)
14
14
 
15
- require 'rubocop/rake_task'
15
+ require "rubocop/rake_task"
16
16
  RuboCop::RakeTask.new do |task|
17
- task.requires << 'rubocop-performance'
18
- task.requires << 'rubocop-rspec'
17
+ task.requires << "rubocop-performance"
18
+ task.requires << "rubocop-rspec"
19
19
  end
@@ -5,6 +5,7 @@ module Permits
5
5
 
6
6
  def copy_application_policy
7
7
  template "create_permits_permissions.rb", "db/migrate/#{Time.current.strftime("%Y%m%d%H%M%S")}_create_permits_permissions.rb"
8
+ template "create_permits_invites.rb", "db/migrate/#{Time.current.strftime("%Y%m%d%H%M%S")}_create_permits_invites.rb"
8
9
  end
9
10
  end
10
11
  end
@@ -0,0 +1,14 @@
1
+ class CreatePermitsInvites < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table :permits_invites, id: :uuid do |t|
4
+ t.references :invited_by, polymorphic: true, index: true, type: :uuid, null: false
5
+ t.references :invitee, polymorphic: true, index: true, type: :uuid, null: true
6
+ t.string :email, null: false
7
+ t.string :aasm_state, null: false
8
+ t.string :slug, null: false
9
+ t.datetime :started_at
10
+ t.datetime :ended_at
11
+ t.timestamps
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ require "active_support/concern"
2
+
3
+ module Permits
4
+ module HasPermissions
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ has_many :permissions, -> { active }, as: :owner, class_name: "::Permits::Permission", dependent: :destroy
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,65 @@
1
+ module Permits
2
+ module Forms
3
+ class InviteForm
4
+ include ActiveModel::Model
5
+ include ActiveModel::Attributes
6
+ include ActiveModel::Validations
7
+
8
+ attribute :invite_id
9
+ attribute :invited
10
+ attribute :token
11
+
12
+ validates :invite_id, presence: true
13
+ validates :invited, presence: true
14
+ validates :token, presence: true
15
+
16
+ def accept
17
+ return false unless valid_invite?
18
+
19
+ ActiveRecord::Base.transaction do
20
+ invite.accept!
21
+ end
22
+ true
23
+ end
24
+
25
+ def decline
26
+ return false unless valid_invite?
27
+
28
+ ActiveRecord::Base.transaction do
29
+ invite.decline!
30
+ end
31
+ true
32
+ end
33
+
34
+ def destroy
35
+ ActiveRecord::Base.transaction do
36
+ invite.permissions.destroy_all
37
+ invite.destroy
38
+ end
39
+ true
40
+ end
41
+
42
+ private
43
+
44
+ def valid_invite?
45
+ return false unless valid?
46
+
47
+ valid_email? && valid_token?
48
+ end
49
+
50
+ def valid_email?
51
+ raise I18n.t("errors.invited_does_not_respond_to_email") unless invited.respond_to?(:email)
52
+
53
+ invite.email == invited.email
54
+ end
55
+
56
+ def valid_token?
57
+ invite.slug == token
58
+ end
59
+
60
+ def invite
61
+ @invite ||= ::Permits::Invite.find(invite_id)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,58 @@
1
+ module Permits
2
+ module Forms
3
+ class NewInviteForm
4
+ include ActiveModel::Model
5
+ include ActiveModel::Attributes
6
+ include ActiveModel::Validations
7
+
8
+ attribute :invited_by
9
+ attribute :email
10
+ attribute :permission_attributes
11
+
12
+ validates :invited_by, presence: true
13
+ validates :email, presence: true
14
+
15
+ def save
16
+ return false unless valid?
17
+
18
+ ActiveRecord::Base.transaction do
19
+ invite.save!
20
+ process_permissions if permission_attributes.present?
21
+ end
22
+ true
23
+ end
24
+
25
+ def invite
26
+ @invite ||= Invite.new(
27
+ invited_by: invited_by,
28
+ email: email,
29
+ started_at: Time.current
30
+ )
31
+ end
32
+
33
+ private
34
+
35
+ def process_permissions
36
+ permission_attributes.each do |resource, permissions|
37
+ policy_class = if resource.respond_to?(:policy_class)
38
+ resource.policy_class
39
+ else
40
+ ::Permits::Policy::Base
41
+ end
42
+ next unless policy_class.authorized?(invited_by, resource, :invite)
43
+
44
+ create_permissions(resource, permissions)
45
+ end
46
+ end
47
+
48
+ def create_permissions(resource, *permissions)
49
+ permissions.flatten.each do |permits|
50
+ invite.permissions.find_or_create_by!(
51
+ resource: resource,
52
+ permits: permits
53
+ )
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,54 @@
1
+ require "timelines"
2
+
3
+ module Permits
4
+ class Invite < ActiveRecord::Base
5
+ self.table_name = "permits_invites"
6
+
7
+ include ::AASM
8
+ include ::Timelines::Ephemeral
9
+ include ::Permits::HasPermissions
10
+
11
+ belongs_to :invited_by, polymorphic: true, required: true
12
+ belongs_to :invitee, polymorphic: true, optional: true
13
+
14
+ validates :email, presence: true
15
+ validates :aasm_state, presence: true
16
+ validates :slug, presence: true
17
+
18
+ scope :active, -> { where(ended_at: nil) }
19
+
20
+ attribute :slug, default: -> { SecureRandom.hex(3).upcase }
21
+
22
+ aasm do
23
+ state :pending, initial: true
24
+ state :accepted
25
+ state :declined
26
+
27
+ event :accept do
28
+ transitions from: :pending, to: :accepted, after: :add_invitee_permissions
29
+ end
30
+
31
+ event :decline do
32
+ transitions from: :pending, to: :declined, after: :end_invite_permissions
33
+ end
34
+ end
35
+
36
+ def add_invitee_permissions
37
+ ActiveRecord::Base.transaction do
38
+ permissions.each do |permission|
39
+ invitee_permission = permission.dup
40
+ invitee_permission.update!(owner: invitee, started_at: Time.current)
41
+ permission.destroy
42
+ end
43
+ end
44
+ end
45
+
46
+ def end_invite_permissions
47
+ ActiveRecord::Base.transaction do
48
+ permissions.each do |permission|
49
+ permission.update!(ended_at: Time.current)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -17,7 +17,7 @@ module Permits
17
17
  def permits_is_permissable
18
18
  return if Permits.config.permits&.include?(permits&.to_sym)
19
19
 
20
- errors.add(:permits, "is not a valid permits for #{resource.class}")
20
+ errors.add(:permits, I18n.t("errors.invalid_permits_param", class: resource.class.name))
21
21
  end
22
22
 
23
23
  scope :permits_any, -> { all }
@@ -29,7 +29,7 @@ module Permits
29
29
  return false unless valid?
30
30
 
31
31
  if respond_to?("#{action}?")
32
- return send("#{action}?")
32
+ send("#{action}?")
33
33
  else
34
34
  has_action_permissions?(action)
35
35
  end
@@ -40,7 +40,7 @@ module Permits
40
40
  def has_action_permissions?(action, for_resource: resource)
41
41
  return false unless owner_permissions.respond_to?("permits_#{action}")
42
42
 
43
- return owner_permissions.send("permits_#{action}").where(resource: for_resource).exists?
43
+ owner_permissions.send("permits_#{action}").where(resource: for_resource).exists?
44
44
  end
45
45
 
46
46
  def owner_permissions
@@ -1,3 +1,3 @@
1
1
  module Permits
2
- VERSION = "0.1.4"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/permits.rb CHANGED
@@ -1,9 +1,15 @@
1
- require "permits/version"
2
- require "permits/railtie"
1
+ require "active_support/configurable"
2
+ require "aasm"
3
+ require "permits/concerns/has_permissions"
4
+ require "permits/forms/new_invite_form"
5
+ require "permits/forms/invite_form"
6
+ require "permits/invite"
3
7
  require "permits/permission"
4
- require "permits/policy/base"
5
8
  require "permits/policy/unauthorized_error"
6
- require "active_support/configurable"
9
+ require "permits/policy/base"
10
+ require "permits/railtie"
11
+ require "permits/version"
12
+ require "timelines"
7
13
 
8
14
  module Permits
9
15
  include ActiveSupport::Configurable
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: permits
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Craig Gilchrist
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-11 00:00:00.000000000 Z
11
+ date: 2024-02-26 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aasm
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 5.5.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 5.5.0
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rails
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -134,8 +148,13 @@ files:
134
148
  - Rakefile
135
149
  - lib/generators/permits/install/USAGE
136
150
  - lib/generators/permits/install/install_generator.rb
151
+ - lib/generators/permits/install/templates/create_permits_invites.rb
137
152
  - lib/generators/permits/install/templates/create_permits_permissions.rb
138
153
  - lib/permits.rb
154
+ - lib/permits/concerns/has_permissions.rb
155
+ - lib/permits/forms/invite_form.rb
156
+ - lib/permits/forms/new_invite_form.rb
157
+ - lib/permits/invite.rb
139
158
  - lib/permits/permission.rb
140
159
  - lib/permits/policy/base.rb
141
160
  - lib/permits/policy/unauthorized_error.rb
@@ -164,7 +183,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
164
183
  - !ruby/object:Gem::Version
165
184
  version: '0'
166
185
  requirements: []
167
- rubygems_version: 3.4.20
186
+ rubygems_version: 3.5.4
168
187
  signing_key:
169
188
  specification_version: 4
170
189
  summary: A User and Role management gem for Rails 7