access_grant 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 +7 -0
- data/.codegraph/.gitignore +5 -0
- data/.rspec +3 -0
- data/.rubocop.yml +98 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +33 -0
- data/CONTRIBUTING.md +99 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +21 -0
- data/README.md +123 -0
- data/Rakefile +12 -0
- data/docs/architecture.md +1157 -0
- data/docs/proposal.md +143 -0
- data/docs/superpowers/plans/2026-09-08-access-grant-v1.md +468 -0
- data/docs/superpowers/plans/2026-09-08-gem-release.md +367 -0
- data/docs/superpowers/specs/2026-09-05-owner-role-design.md +271 -0
- data/docs/superpowers/specs/2026-09-07-proposal-review.md +71 -0
- data/docs/superpowers/specs/2026-09-07-usage-scenarios.md +301 -0
- data/docs/superpowers/specs/2026-09-08-gem-release-design.md +82 -0
- data/lib/access_grant/catalog/dsl.rb +138 -0
- data/lib/access_grant/catalog.rb +76 -0
- data/lib/access_grant/configuration.rb +55 -0
- data/lib/access_grant/controller_methods.rb +104 -0
- data/lib/access_grant/models/permission.rb +36 -0
- data/lib/access_grant/models/role.rb +152 -0
- data/lib/access_grant/models/role_permission.rb +11 -0
- data/lib/access_grant/owner.rb +144 -0
- data/lib/access_grant/permission_key.rb +29 -0
- data/lib/access_grant/railtie.rb +17 -0
- data/lib/access_grant/recovery.rb +90 -0
- data/lib/access_grant/sync.rb +68 -0
- data/lib/access_grant/tenant.rb +47 -0
- data/lib/access_grant/user.rb +102 -0
- data/lib/access_grant/version.rb +5 -0
- data/lib/access_grant.rb +125 -0
- data/lib/generators/access_grant/install/install_generator.rb +22 -0
- data/lib/generators/access_grant/install/templates/create_access_grant_tables.rb.tt +39 -0
- data/lib/generators/access_grant/setup/setup_generator.rb +188 -0
- data/lib/generators/access_grant/setup/templates/access_grant.rb.tt +80 -0
- data/lib/generators/access_grant/setup/templates/create_access_grant_user_roles.rb.tt +14 -0
- data/lib/generators/access_grant/setup/templates/permissions.rb.tt +10 -0
- data/lib/generators/access_grant/setup/templates/roles.rb.tt +27 -0
- data/lib/tasks/access_grant_tasks.rake +27 -0
- metadata +121 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Boot wiring for AccessGrant. Every config.* option is listed below with
|
|
4
|
+
# defaults and examples — see docs/architecture.md Configuration reference.
|
|
5
|
+
|
|
6
|
+
AccessGrant.configure do |config|
|
|
7
|
+
# --- tenant_class (String or nil; default: nil = single-tenant) ---
|
|
8
|
+
# Host model that owns roles. When set, roles get a tenant FK and
|
|
9
|
+
# permitted? requires tenant:.
|
|
10
|
+
<% if multi_tenant? -%>
|
|
11
|
+
config.tenant_class = "<%= tenant_class %>"
|
|
12
|
+
# Single-tenant: omit or set nil
|
|
13
|
+
# config.tenant_class = nil
|
|
14
|
+
<% else -%>
|
|
15
|
+
# config.tenant_class = "Organization"
|
|
16
|
+
config.tenant_class = nil
|
|
17
|
+
<% end -%>
|
|
18
|
+
|
|
19
|
+
# --- user_class (String; default: "User") ---
|
|
20
|
+
# Host model that receives roles and permitted?.
|
|
21
|
+
config.user_class = "<%= user_class %>"
|
|
22
|
+
# config.user_class = "Account"
|
|
23
|
+
|
|
24
|
+
# --- owner_role (Symbol: :protected | :bypass | :both | :none; default: :protected) ---
|
|
25
|
+
# How privileged the Owner role is.
|
|
26
|
+
config.owner_role = :<%= owner_role %>
|
|
27
|
+
# config.owner_role = :none # no special Owner; grant_owner! raises
|
|
28
|
+
|
|
29
|
+
# --- owner_role_name (String; default: "Owner") ---
|
|
30
|
+
# Reserved role name for the privileged floor (case-insensitive).
|
|
31
|
+
config.owner_role_name = "Owner"
|
|
32
|
+
# config.owner_role_name = "Super Admin"
|
|
33
|
+
|
|
34
|
+
# --- tables (Hash; default: short names) ---
|
|
35
|
+
# Physical table names chosen by setup --tables=auto|simple|prefixed.
|
|
36
|
+
# If install migration still uses short names but these are prefixed,
|
|
37
|
+
# edit the install migration before db:migrate.
|
|
38
|
+
config.tables = {
|
|
39
|
+
roles: "<%= tables[:roles] %>",
|
|
40
|
+
permissions: "<%= tables[:permissions] %>",
|
|
41
|
+
role_permissions: "<%= tables[:role_permissions] %>",
|
|
42
|
+
user_roles: "<%= tables[:user_roles] %>"
|
|
43
|
+
}
|
|
44
|
+
# After collision / --tables=prefixed:
|
|
45
|
+
# config.tables = {
|
|
46
|
+
# roles: "access_grant_roles",
|
|
47
|
+
# permissions: "access_grant_permissions",
|
|
48
|
+
# role_permissions: "access_grant_role_permissions",
|
|
49
|
+
# user_roles: "access_grant_user_roles"
|
|
50
|
+
# }
|
|
51
|
+
|
|
52
|
+
# --- default_permission_actions (Array<String>) ---
|
|
53
|
+
# Actions emitted for each `resource :name` in the catalog DSL.
|
|
54
|
+
config.default_permission_actions = %w[index show create update destroy]
|
|
55
|
+
# config.default_permission_actions = %w[index show create update destroy search attach detach]
|
|
56
|
+
|
|
57
|
+
# --- current_user_method (Symbol; default: :current_user) ---
|
|
58
|
+
# Controller method used by access_grant_authorize! for the acting user.
|
|
59
|
+
config.current_user_method = :current_user
|
|
60
|
+
# config.current_user_method = :current_account
|
|
61
|
+
|
|
62
|
+
# --- current_tenant_method (Symbol; default: :current_tenant) ---
|
|
63
|
+
# Controller method for the tenant (multi-tenant). Host must define it.
|
|
64
|
+
# Unused when tenant_class is nil.
|
|
65
|
+
config.current_tenant_method = :current_tenant
|
|
66
|
+
# config.current_tenant_method = :current_organization
|
|
67
|
+
|
|
68
|
+
# --- on_tenant_created (Proc or nil; default: nil) ---
|
|
69
|
+
# Usually set in config/access_grant/roles.rb — seed default roles, not Owner.
|
|
70
|
+
# config.on_tenant_created = ->(tenant) { ... }
|
|
71
|
+
|
|
72
|
+
# --- recover_access (Proc or nil; default: built-in Recovery.grant_role!) ---
|
|
73
|
+
# Ops lockout recovery for rake access_grant:grant_role.
|
|
74
|
+
# config.recover_access = ->(role_name:, user_id:, tenant_id: nil) {
|
|
75
|
+
# AccessGrant::Recovery.grant_role!(role_name:, user_id:, tenant_id:)
|
|
76
|
+
# }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Load catalog + roles after boot config.
|
|
80
|
+
Rails.root.glob("config/access_grant/**/*.rb").sort.each { |f| require f }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
4
|
+
def change
|
|
5
|
+
create_table :<%= @user_roles_table %>, id: false do |t|
|
|
6
|
+
t.bigint :user_id, null: false
|
|
7
|
+
t.bigint :role_id, null: false
|
|
8
|
+
end
|
|
9
|
+
# permitted? starts from user; unique prevents duplicate assignments
|
|
10
|
+
add_index :<%= @user_roles_table %>, %i[user_id role_id], unique: true
|
|
11
|
+
# last-Owner assignment_count / reverse: users for a role
|
|
12
|
+
add_index :<%= @user_roles_table %>, :role_id
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Permission catalog — code is the source of truth for keys.
|
|
4
|
+
# Sync into the DB: bundle exec rake access_grant:sync_permissions
|
|
5
|
+
|
|
6
|
+
AccessGrant.permissions do
|
|
7
|
+
resource :invoices do
|
|
8
|
+
action :couple, description: "Can couple invoices together"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Default roles seeded when a tenant is created (multi-tenant).
|
|
4
|
+
# Edit this file freely — it is your app's starting point, not Owner assignment.
|
|
5
|
+
# Owner stays host-owned: org.grant_owner!(user).
|
|
6
|
+
#
|
|
7
|
+
# Prerequisite: run `bundle exec rake access_grant:sync_permissions` so the
|
|
8
|
+
# permission catalog exists in the DB before roles reference keys.
|
|
9
|
+
|
|
10
|
+
AccessGrant.configure do |config|
|
|
11
|
+
config.on_tenant_created = ->(tenant) do
|
|
12
|
+
# Per-resource Viewer + Manager roles from synced Permission.category
|
|
13
|
+
# (e.g. resource :invoices → "Invoices Viewer", "Invoices Manager").
|
|
14
|
+
# Create-only: existing role names for this tenant are left alone.
|
|
15
|
+
AccessGrant::Role.ensure_resource_defaults_for!(tenant)
|
|
16
|
+
|
|
17
|
+
# Or define explicit names/keys yourself:
|
|
18
|
+
# AccessGrant::Role.ensure_defaults_for!(
|
|
19
|
+
# tenant,
|
|
20
|
+
# "Admin" => %w[invoices.index invoices.show invoices.update],
|
|
21
|
+
# "Member" => %w[invoices.index invoices.show]
|
|
22
|
+
# )
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Single-tenant: there is no tenant create callback. From db/seeds.rb (after sync):
|
|
27
|
+
# AccessGrant::Role.ensure_resource_defaults_for!(nil)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :access_grant do
|
|
4
|
+
# Sync {AccessGrant.catalog} into the permissions table. Never deletes keys.
|
|
5
|
+
# For :protected/:both, reattaches all keys to Owner roles.
|
|
6
|
+
# Run on every deploy after migrate.
|
|
7
|
+
desc "Upsert catalog permissions into the database (never deletes)"
|
|
8
|
+
task sync_permissions: :environment do
|
|
9
|
+
AccessGrant::Sync.call
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Ops recovery: ROLE=Owner USER_ID=1 TENANT_ID=42 rake access_grant:grant_role
|
|
13
|
+
# Uses config.recover_access when set, else AccessGrant::Recovery.grant_role!.
|
|
14
|
+
desc "Grant a named role to a user (ops lockout recovery). Env: ROLE, USER_ID, optional TENANT_ID"
|
|
15
|
+
task grant_role: :environment do
|
|
16
|
+
role_name = ENV.fetch("ROLE") { raise "ROLE is required" }
|
|
17
|
+
user_id = Integer(ENV.fetch("USER_ID") { raise "USER_ID is required" })
|
|
18
|
+
tenant_id = ENV["TENANT_ID"]&.then { |id| Integer(id) }
|
|
19
|
+
|
|
20
|
+
callable = AccessGrant.config.recover_access
|
|
21
|
+
if callable
|
|
22
|
+
callable.call(role_name: role_name, user_id: user_id, tenant_id: tenant_id)
|
|
23
|
+
else
|
|
24
|
+
AccessGrant::Recovery.grant_role!(role_name: role_name, user_id: user_id, tenant_id: tenant_id)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: access_grant
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- SahSantoshh
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: railties
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '7.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '7.0'
|
|
40
|
+
description: |
|
|
41
|
+
AccessGrant gives Rails apps a role-based access control system where permissions
|
|
42
|
+
live in the database and can be reassigned to roles by tenant admins at runtime,
|
|
43
|
+
with no deploy required. Unlike Pundit/CanCanCan/Action Policy (which hardcode
|
|
44
|
+
permission logic in Ruby policy/ability classes) or Rolify (which manages role
|
|
45
|
+
assignment but has no concept of permissions), AccessGrant ships a code-defined
|
|
46
|
+
permission catalog synced into the database, dynamic per-tenant roles, and a
|
|
47
|
+
permitted?(key) check.
|
|
48
|
+
email:
|
|
49
|
+
- sahsantoshh@gmail.com
|
|
50
|
+
executables: []
|
|
51
|
+
extensions: []
|
|
52
|
+
extra_rdoc_files: []
|
|
53
|
+
files:
|
|
54
|
+
- ".codegraph/.gitignore"
|
|
55
|
+
- ".rspec"
|
|
56
|
+
- ".rubocop.yml"
|
|
57
|
+
- ".ruby-version"
|
|
58
|
+
- CHANGELOG.md
|
|
59
|
+
- CONTRIBUTING.md
|
|
60
|
+
- Gemfile
|
|
61
|
+
- LICENSE.txt
|
|
62
|
+
- README.md
|
|
63
|
+
- Rakefile
|
|
64
|
+
- docs/architecture.md
|
|
65
|
+
- docs/proposal.md
|
|
66
|
+
- docs/superpowers/plans/2026-09-08-access-grant-v1.md
|
|
67
|
+
- docs/superpowers/plans/2026-09-08-gem-release.md
|
|
68
|
+
- docs/superpowers/specs/2026-09-05-owner-role-design.md
|
|
69
|
+
- docs/superpowers/specs/2026-09-07-proposal-review.md
|
|
70
|
+
- docs/superpowers/specs/2026-09-07-usage-scenarios.md
|
|
71
|
+
- docs/superpowers/specs/2026-09-08-gem-release-design.md
|
|
72
|
+
- lib/access_grant.rb
|
|
73
|
+
- lib/access_grant/catalog.rb
|
|
74
|
+
- lib/access_grant/catalog/dsl.rb
|
|
75
|
+
- lib/access_grant/configuration.rb
|
|
76
|
+
- lib/access_grant/controller_methods.rb
|
|
77
|
+
- lib/access_grant/models/permission.rb
|
|
78
|
+
- lib/access_grant/models/role.rb
|
|
79
|
+
- lib/access_grant/models/role_permission.rb
|
|
80
|
+
- lib/access_grant/owner.rb
|
|
81
|
+
- lib/access_grant/permission_key.rb
|
|
82
|
+
- lib/access_grant/railtie.rb
|
|
83
|
+
- lib/access_grant/recovery.rb
|
|
84
|
+
- lib/access_grant/sync.rb
|
|
85
|
+
- lib/access_grant/tenant.rb
|
|
86
|
+
- lib/access_grant/user.rb
|
|
87
|
+
- lib/access_grant/version.rb
|
|
88
|
+
- lib/generators/access_grant/install/install_generator.rb
|
|
89
|
+
- lib/generators/access_grant/install/templates/create_access_grant_tables.rb.tt
|
|
90
|
+
- lib/generators/access_grant/setup/setup_generator.rb
|
|
91
|
+
- lib/generators/access_grant/setup/templates/access_grant.rb.tt
|
|
92
|
+
- lib/generators/access_grant/setup/templates/create_access_grant_user_roles.rb.tt
|
|
93
|
+
- lib/generators/access_grant/setup/templates/permissions.rb.tt
|
|
94
|
+
- lib/generators/access_grant/setup/templates/roles.rb.tt
|
|
95
|
+
- lib/tasks/access_grant_tasks.rake
|
|
96
|
+
homepage: https://github.com/SahSantoshh/access_grant
|
|
97
|
+
licenses:
|
|
98
|
+
- MIT
|
|
99
|
+
metadata:
|
|
100
|
+
homepage_uri: https://github.com/SahSantoshh/access_grant
|
|
101
|
+
source_code_uri: https://github.com/SahSantoshh/access_grant
|
|
102
|
+
changelog_uri: https://github.com/SahSantoshh/access_grant/blob/main/CHANGELOG.md
|
|
103
|
+
rubygems_mfa_required: 'true'
|
|
104
|
+
rdoc_options: []
|
|
105
|
+
require_paths:
|
|
106
|
+
- lib
|
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
108
|
+
requirements:
|
|
109
|
+
- - ">="
|
|
110
|
+
- !ruby/object:Gem::Version
|
|
111
|
+
version: 3.1.0
|
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">="
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0'
|
|
117
|
+
requirements: []
|
|
118
|
+
rubygems_version: 3.6.9
|
|
119
|
+
specification_version: 4
|
|
120
|
+
summary: Dynamic, database-backed, per-tenant role and permission management for Rails.
|
|
121
|
+
test_files: []
|