super_auth 0.4.0 → 0.7.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 +4 -4
- data/CHANGELOG.md +57 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +8 -1
- data/README.md +97 -31
- data/USAGE.md +17 -15
- data/config/routes.rb +9 -71
- data/db/migrate/10_add_super_auth_label_to_resources.rb +13 -0
- data/db/migrate_activerecord/20250101000010_add_super_auth_label_to_super_auth_resources.rb +5 -0
- data/exe/super_auth-editor +9 -0
- data/lib/generators/super_auth/install/templates/README +15 -10
- data/lib/generators/super_auth/rls/templates/migration.rb.erb +2 -0
- data/lib/super_auth/active_record/by_current_user.rb +1 -1
- data/lib/super_auth/active_record/resource.rb +41 -0
- data/lib/super_auth/active_record/user.rb +3 -1
- data/lib/super_auth/authorization.rb +12 -0
- data/lib/super_auth/editor/cli.rb +91 -0
- data/lib/super_auth/editor/index.html +423 -0
- data/lib/super_auth/editor/seed.rb +170 -0
- data/lib/super_auth/editor.rb +273 -0
- data/lib/super_auth/railtie.rb +0 -2
- data/lib/super_auth/rls.rb +164 -34
- data/lib/super_auth/user.rb +3 -1
- data/lib/super_auth/version.rb +1 -1
- data/lib/super_auth.rb +42 -5
- data/lib/tasks/super_auth_tasks.rake +28 -0
- metadata +11 -8
- data/VISUALIZATION.md +0 -58
- data/app/controllers/super_auth/graph_controller.rb +0 -654
- data/app/views/super_auth/graph/index.html.erb +0 -1408
- data/super_auth.gemspec +0 -35
- data/visualization.html +0 -747
data/lib/super_auth/rls.rb
CHANGED
|
@@ -3,12 +3,26 @@
|
|
|
3
3
|
# ByCurrentUser filters queries at the ORM layer; RLS enforces the same rule
|
|
4
4
|
# inside Postgres, so raw SQL, `unscoped`, and non-Ruby clients are subject
|
|
5
5
|
# to it too — enforcing apps don't load this gem at all. Identity is
|
|
6
|
-
# asserted per transaction by
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
6
|
+
# asserted per transaction by two SQL functions installed by `enable`:
|
|
7
|
+
#
|
|
8
|
+
# super_auth_become(user_external_id, user_external_type, user_id)
|
|
9
|
+
# asserts a user's identity. Executable by PUBLIC.
|
|
10
|
+
# super_auth_system()
|
|
11
|
+
# asserts system context, which bypasses every policy. EXECUTE is
|
|
12
|
+
# revoked from PUBLIC; `grant_system(role)` hands it to the roles that
|
|
13
|
+
# may bypass.
|
|
14
|
+
#
|
|
15
|
+
# `enable` also grants every role SELECT on the gem's own tables, which the
|
|
16
|
+
# policies and the user models read, so a runtime role needs privileges on
|
|
17
|
+
# the application's tables and nothing else.
|
|
18
|
+
#
|
|
19
|
+
# Both set transaction-local identity settings plus a stamp of the current
|
|
20
|
+
# transaction id, and every policy requires a stamp from the current
|
|
21
|
+
# transaction. Identity therefore cannot outlive its transaction or leak
|
|
22
|
+
# across pooled connections — a query without a fresh assertion sees no
|
|
23
|
+
# rows. Both raise if the calling role is a superuser or has BYPASSRLS:
|
|
24
|
+
# Postgres exempts those roles from every policy, so an identity assertion
|
|
25
|
+
# from one would protect nothing while looking like it does.
|
|
12
26
|
module SuperAuth
|
|
13
27
|
module RLS
|
|
14
28
|
POLICY = "super_auth".freeze
|
|
@@ -16,7 +30,7 @@ module SuperAuth
|
|
|
16
30
|
class << self
|
|
17
31
|
# Enable RLS on an app table with a policy mirroring ByCurrentUser:
|
|
18
32
|
# type-level authorization rows (resource_external_id IS NULL) act as a
|
|
19
|
-
# wildcard, per-record rows match on id, and system
|
|
33
|
+
# wildcard, per-record rows match on id, and system context bypasses.
|
|
20
34
|
#
|
|
21
35
|
# One deliberate divergence: INSERTs are also gated. The policy is
|
|
22
36
|
# FOR ALL with no WITH CHECK, so Postgres reuses its USING expression
|
|
@@ -25,7 +39,8 @@ module SuperAuth
|
|
|
25
39
|
# system context.
|
|
26
40
|
def enable(table, resource_type:, db: SuperAuth.db)
|
|
27
41
|
postgres!(db)
|
|
28
|
-
|
|
42
|
+
create_functions(db)
|
|
43
|
+
grant_runtime_reads(db)
|
|
29
44
|
t = db.literal(Sequel.identifier(table.to_s))
|
|
30
45
|
db.run "ALTER TABLE #{t} ENABLE ROW LEVEL SECURITY"
|
|
31
46
|
# FORCE: apply the policy even when the app connects as the table owner
|
|
@@ -54,9 +69,9 @@ module SuperAuth
|
|
|
54
69
|
SQL
|
|
55
70
|
end
|
|
56
71
|
|
|
57
|
-
# Drops the table's policy; the shared
|
|
58
|
-
#
|
|
59
|
-
#
|
|
72
|
+
# Drops the table's policy; the shared functions are left in place
|
|
73
|
+
# (other tables may still be protected, and they are harmless on their
|
|
74
|
+
# own).
|
|
60
75
|
def disable(table, db: SuperAuth.db)
|
|
61
76
|
postgres!(db)
|
|
62
77
|
t = db.literal(Sequel.identifier(table.to_s))
|
|
@@ -70,51 +85,166 @@ module SuperAuth
|
|
|
70
85
|
# (BEGIN; SELECT super_auth_become(...); queries; COMMIT). Sequel and
|
|
71
86
|
# ActiveRecord queries inside the block share the transaction's
|
|
72
87
|
# connection, so the policies see the identity; it dies with the
|
|
73
|
-
# transaction.
|
|
74
|
-
#
|
|
75
|
-
|
|
88
|
+
# transaction. A user whose `system?` is true asserts system context
|
|
89
|
+
# through super_auth_system() instead, which the connection's role
|
|
90
|
+
# must have been granted EXECUTE on.
|
|
91
|
+
#
|
|
92
|
+
# Inside an enclosing transaction (the caller's, or an outer `as`) it
|
|
93
|
+
# joins that transaction instead of opening one, and it puts the
|
|
94
|
+
# enclosing identity back when the block ends, however it ends: the
|
|
95
|
+
# innermost assertion wins inside the block and nothing else afterwards.
|
|
96
|
+
# This touches only the database settings; SuperAuth.as is the call that
|
|
97
|
+
# also sets SuperAuth.current_user.
|
|
98
|
+
#
|
|
99
|
+
# Transaction options pass through to Sequel's transaction. One matters
|
|
100
|
+
# for a wrapper that exists only to carry an identity:
|
|
101
|
+
# auto_savepoint: true every nested transaction becomes a savepoint
|
|
102
|
+
# (the ActiveRecord bridge turns this into
|
|
103
|
+
# joinable: false), so a save inside the block
|
|
104
|
+
# commits on its own and its after_commit hooks
|
|
105
|
+
# fire then, not at the end of the block.
|
|
106
|
+
# Whether a write survives the block raising is the caller's policy, not
|
|
107
|
+
# this wrapper's: rescue inside the block to keep it, or let the
|
|
108
|
+
# exception out to roll it back.
|
|
109
|
+
def as(user, db: SuperAuth.db, **transaction_options)
|
|
76
110
|
postgres!(db)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
111
|
+
# Outside a transaction the settings die at COMMIT and there is nothing
|
|
112
|
+
# to restore; inside one, the enclosing identity must survive the block.
|
|
113
|
+
enclosing = db.in_transaction? ? identity(db) : nil
|
|
114
|
+
db.transaction(**transaction_options) do
|
|
115
|
+
assert(user, db: db)
|
|
116
|
+
begin
|
|
117
|
+
yield
|
|
118
|
+
ensure
|
|
119
|
+
restore(enclosing, db) if enclosing
|
|
120
|
+
end
|
|
84
121
|
end
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Assert `user`'s identity in the transaction the caller already holds,
|
|
125
|
+
# without opening one: the SELECT super_auth_become(...) half of the
|
|
126
|
+
# contract, or super_auth_system() for a user whose `system?` is true.
|
|
127
|
+
# For re-asserting mid-transaction, and for code that manages its own
|
|
128
|
+
# transaction and only needs the identity in it. Outside a transaction
|
|
129
|
+
# the settings die with the statement, so it protects nothing there.
|
|
130
|
+
def assert(user, db: SuperAuth.db)
|
|
131
|
+
postgres!(db)
|
|
132
|
+
if user.respond_to?(:system?) && user.system?
|
|
133
|
+
db.get(Sequel.function(:super_auth_system))
|
|
134
|
+
else
|
|
135
|
+
db.get(Sequel.function(:super_auth_become, *become_args(user)))
|
|
88
136
|
end
|
|
89
137
|
end
|
|
90
138
|
|
|
139
|
+
# Whether `enable` has run on this database: both identity functions
|
|
140
|
+
# exist with their current signatures. One query per call, so a hot
|
|
141
|
+
# path memoises it. False on a non-Postgres database, where RLS cannot
|
|
142
|
+
# be installed.
|
|
143
|
+
def installed?(db: SuperAuth.db)
|
|
144
|
+
return false unless db.database_type == :postgres
|
|
145
|
+
db.get(Sequel.lit("to_regprocedure('super_auth_become(text,text,text)') IS NOT NULL AND to_regprocedure('super_auth_system()') IS NOT NULL"))
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Allow `role` to assert system context: SuperAuth.as with a user whose
|
|
149
|
+
# system? is true, or SELECT super_auth_system() directly. enable revokes
|
|
150
|
+
# this from PUBLIC; grant it to the roles that run migrations, seeds and
|
|
151
|
+
# admin jobs, and to nothing else.
|
|
152
|
+
def grant_system(role, db: SuperAuth.db)
|
|
153
|
+
postgres!(db)
|
|
154
|
+
db.run "GRANT EXECUTE ON FUNCTION super_auth_system() TO #{db.literal(Sequel.identifier(role.to_s))}"
|
|
155
|
+
end
|
|
156
|
+
|
|
91
157
|
private
|
|
92
158
|
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
|
|
159
|
+
# What any runtime role needs on the gem's own tables: the policies read
|
|
160
|
+
# super_auth_authorizations as the querying role, and the user models'
|
|
161
|
+
# system? reads super_auth_users. Granting PUBLIC makes enable the only
|
|
162
|
+
# setup step; a deployment that wants these tables private can REVOKE
|
|
163
|
+
# from PUBLIC and grant per role.
|
|
164
|
+
def grant_runtime_reads(db)
|
|
165
|
+
db.run "GRANT SELECT ON super_auth_authorizations, super_auth_users TO PUBLIC"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Refuses an identity assertion from a role Postgres exempts from row
|
|
169
|
+
# security: the policies would apply to nobody while everything looked
|
|
170
|
+
# enforced. Checks the effective role, so a superuser session that has
|
|
171
|
+
# SET ROLE to an application role passes.
|
|
172
|
+
SUPERUSER_GUARD = <<~SQL.freeze
|
|
173
|
+
IF (SELECT rolsuper OR rolbypassrls FROM pg_roles WHERE rolname = current_user) THEN
|
|
174
|
+
RAISE EXCEPTION 'super_auth: role % is a superuser or has BYPASSRLS, so row-level security does not apply to it and asserting an identity would protect nothing. Connect as a regular role.', current_user
|
|
175
|
+
USING ERRCODE = 'invalid_authorization_specification';
|
|
176
|
+
END IF;
|
|
177
|
+
SQL
|
|
178
|
+
|
|
179
|
+
# Two shared functions per database; clients assert identity by calling
|
|
180
|
+
# one of them inside their transaction. CREATE OR REPLACE keeps enable
|
|
181
|
+
# idempotent. The pre-0.5 four-argument super_auth_become carried the
|
|
182
|
+
# system bypass as its last parameter; a REPLACE with a different
|
|
183
|
+
# signature would leave that overload in place, so it is dropped.
|
|
184
|
+
def create_functions(db)
|
|
185
|
+
db.run "DROP FUNCTION IF EXISTS super_auth_become(text, text, text, boolean)"
|
|
97
186
|
db.run <<~SQL
|
|
98
187
|
CREATE OR REPLACE FUNCTION super_auth_become(
|
|
99
188
|
user_external_id text DEFAULT NULL,
|
|
100
189
|
user_external_type text DEFAULT NULL,
|
|
101
|
-
user_id text DEFAULT NULL
|
|
102
|
-
system boolean DEFAULT false
|
|
190
|
+
user_id text DEFAULT NULL
|
|
103
191
|
) RETURNS void LANGUAGE plpgsql AS $$
|
|
104
192
|
BEGIN
|
|
193
|
+
#{SUPERUSER_GUARD}
|
|
105
194
|
PERFORM set_config('super_auth.user_id', COALESCE(user_id, ''), true),
|
|
106
195
|
set_config('super_auth.user_external_id', COALESCE(user_external_id, ''), true),
|
|
107
196
|
set_config('super_auth.user_external_type', COALESCE(user_external_type, ''), true),
|
|
108
|
-
set_config('super_auth.system',
|
|
197
|
+
set_config('super_auth.system', '', true),
|
|
198
|
+
set_config('super_auth.xid', pg_current_xact_id()::text, true);
|
|
199
|
+
END
|
|
200
|
+
$$;
|
|
201
|
+
SQL
|
|
202
|
+
db.run <<~SQL
|
|
203
|
+
CREATE OR REPLACE FUNCTION super_auth_system() RETURNS void LANGUAGE plpgsql AS $$
|
|
204
|
+
BEGIN
|
|
205
|
+
#{SUPERUSER_GUARD}
|
|
206
|
+
PERFORM set_config('super_auth.user_id', '', true),
|
|
207
|
+
set_config('super_auth.user_external_id', '', true),
|
|
208
|
+
set_config('super_auth.user_external_type', '', true),
|
|
209
|
+
set_config('super_auth.system', 'true', true),
|
|
109
210
|
set_config('super_auth.xid', pg_current_xact_id()::text, true);
|
|
110
211
|
END
|
|
111
212
|
$$;
|
|
112
213
|
SQL
|
|
214
|
+
# Bypass is opt-in per role: GRANT EXECUTE ON FUNCTION super_auth_system() TO <role>.
|
|
215
|
+
db.run "REVOKE EXECUTE ON FUNCTION super_auth_system() FROM PUBLIC"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# super_auth_become's three arguments for `user`: a SuperAuth user
|
|
219
|
+
# record goes by user_id, any other object by id and class name, nil by
|
|
220
|
+
# nothing, an identity no authorization matches.
|
|
221
|
+
def become_args(user)
|
|
222
|
+
if user.nil?
|
|
223
|
+
[nil, nil, nil]
|
|
224
|
+
elsif SuperAuth.internal_user?(user)
|
|
225
|
+
[nil, nil, user.id.to_s]
|
|
226
|
+
else
|
|
227
|
+
[user.id.to_s, user.class.name, nil]
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# The five transaction-local settings the policies read, in one order.
|
|
232
|
+
SETTINGS = %w[
|
|
233
|
+
super_auth.user_id super_auth.user_external_id super_auth.user_external_type
|
|
234
|
+
super_auth.system super_auth.xid
|
|
235
|
+
].freeze
|
|
236
|
+
|
|
237
|
+
def identity(db)
|
|
238
|
+
db.dataset.get(SETTINGS.each_with_index.map { |name, i| Sequel.function(:current_setting, name, true).as(:"s#{i}") })
|
|
113
239
|
end
|
|
114
240
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
241
|
+
# Writes the settings back directly: the values were read from this same
|
|
242
|
+
# transaction, so the stamp is still the current one, and no role is
|
|
243
|
+
# granted anything it could not already set.
|
|
244
|
+
def restore(values, db)
|
|
245
|
+
db.dataset.get(SETTINGS.zip(values).each_with_index.map { |(name, value), i| Sequel.function(:set_config, name, value.to_s, true).as(:"s#{i}") })
|
|
246
|
+
rescue Sequel::DatabaseError
|
|
247
|
+
# The block aborted the transaction; its rollback discards the settings.
|
|
118
248
|
end
|
|
119
249
|
|
|
120
250
|
def postgres!(db)
|
data/lib/super_auth/user.rb
CHANGED
|
@@ -2,7 +2,9 @@ class SuperAuth::User < Sequel::Model(:super_auth_users)
|
|
|
2
2
|
one_to_many :edges
|
|
3
3
|
one_to_many :resources
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# A read: runtime roles only get SELECT on this table. `.system` creates
|
|
6
|
+
# the row when missing and belongs to migrations, seeds and consoles.
|
|
7
|
+
def system? = self.class.first(name: "system") == self
|
|
6
8
|
def self.system = find_or_create(name: "system")
|
|
7
9
|
|
|
8
10
|
dataset_module do
|
data/lib/super_auth/version.rb
CHANGED
data/lib/super_auth.rb
CHANGED
|
@@ -42,6 +42,23 @@ module SuperAuth
|
|
|
42
42
|
external_id_type == :string ? String : external_id_type
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
# The human name of the application record behind a node, by convention
|
|
46
|
+
# rather than configuration: a model says it explicitly with
|
|
47
|
+
# `super_auth_label`, otherwise `name` then `title` are tried, and a model
|
|
48
|
+
# with none of them has no label. This is also the name of the column the
|
|
49
|
+
# derivation is stored in, since `label` is a name applications want for
|
|
50
|
+
# themselves. Deliberately never `to_s` — the label sits where the editor
|
|
51
|
+
# otherwise renders `Type#id`, and "#<Claim:0x000055…>" is worse than the id
|
|
52
|
+
# it would replace.
|
|
53
|
+
def self.label_for(record)
|
|
54
|
+
%i[super_auth_label name title].each do |method|
|
|
55
|
+
next unless record.respond_to?(method)
|
|
56
|
+
value = record.public_send(method)
|
|
57
|
+
return value.to_s unless value.nil? || value.to_s.empty?
|
|
58
|
+
end
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
61
|
+
|
|
45
62
|
def self.load
|
|
46
63
|
require "super_auth/authorization"
|
|
47
64
|
require "super_auth/edge"
|
|
@@ -95,11 +112,31 @@ module SuperAuth
|
|
|
95
112
|
raise Error, "Failed to uninstall migrations: #{e.message}"
|
|
96
113
|
end
|
|
97
114
|
|
|
98
|
-
# Run the block
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
|
|
102
|
-
|
|
115
|
+
# Run the block as `user` in both layers: SuperAuth.current_user, which the
|
|
116
|
+
# ByCurrentUser scope reads, and the database identity the RLS policies read
|
|
117
|
+
# (see SuperAuth::RLS.as). Both are restored on the way out, whether the
|
|
118
|
+
# block returns, raises, or was nested inside another `as`. Passing nil runs
|
|
119
|
+
# the block with no user in either layer. Postgres only, since the database
|
|
120
|
+
# half is. Keyword options (auto_savepoint:, ...) go to SuperAuth::RLS.as.
|
|
121
|
+
# current_user is assigned inside the transaction, after the database
|
|
122
|
+
# identity, so an application that hooks the writer to re-assert does so on
|
|
123
|
+
# the connection that holds the transaction.
|
|
124
|
+
def self.as(user, db: SuperAuth.db, **options)
|
|
125
|
+
previous = current_user
|
|
126
|
+
SuperAuth::RLS.as(user, db: db, **options) do
|
|
127
|
+
self.current_user = user
|
|
128
|
+
yield
|
|
129
|
+
end
|
|
130
|
+
ensure
|
|
131
|
+
self.current_user = previous
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Both user models are internal: their id is the user_id that the policies
|
|
135
|
+
# and ByCurrentUser match on. Anything else is an application object,
|
|
136
|
+
# matched by id and class name.
|
|
137
|
+
def self.internal_user?(user)
|
|
138
|
+
(defined?(SuperAuth::ActiveRecord::User) && user.is_a?(SuperAuth::ActiveRecord::User)) ||
|
|
139
|
+
(defined?(SuperAuth::User) && user.is_a?(SuperAuth::User))
|
|
103
140
|
end
|
|
104
141
|
|
|
105
142
|
def self.current_user=(user)
|
|
@@ -6,6 +6,34 @@ namespace :super_auth do
|
|
|
6
6
|
puts "Done"
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
+
namespace :labels do
|
|
10
|
+
desc "Re-derive super_auth_resources.super_auth_label from the application records"
|
|
11
|
+
task backfill: :environment do
|
|
12
|
+
SuperAuth.load
|
|
13
|
+
backfill = lambda do
|
|
14
|
+
changed = 0
|
|
15
|
+
SuperAuth::ActiveRecord::Resource.where.not(external_id: nil).find_each do |resource|
|
|
16
|
+
before = resource.super_auth_label
|
|
17
|
+
resource.refresh_label!
|
|
18
|
+
changed += 1 if resource.super_auth_label != before
|
|
19
|
+
end
|
|
20
|
+
changed
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The application tables this reads are the ones RLS protects, so a run
|
|
24
|
+
# with no identity derives nil for exactly the rows that matter most and
|
|
25
|
+
# then reports success. Assert the system identity where RLS is
|
|
26
|
+
# installed; where it is not, there is nothing to assert.
|
|
27
|
+
changed =
|
|
28
|
+
if SuperAuth::RLS.installed?
|
|
29
|
+
SuperAuth.as(SuperAuth::ActiveRecord::User.system) { backfill.call }
|
|
30
|
+
else
|
|
31
|
+
backfill.call
|
|
32
|
+
end
|
|
33
|
+
puts "Labelled #{changed} resources"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
9
37
|
task :rollback => :environment do
|
|
10
38
|
raise "You must define SUPER_AUTH_DATABASE_URL in your environment for this to work" if ENV['SUPER_AUTH_DATABASE_URL'].nil? || ENV['SUPER_AUTH_DATABASE_URL'].empty?
|
|
11
39
|
SuperAuth.uninstall_migrations
|
metadata
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: super_auth
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jonathan Frias
|
|
8
|
-
bindir:
|
|
8
|
+
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
@@ -40,7 +40,8 @@ dependencies:
|
|
|
40
40
|
description: Simple, yet super powerful authorization for you application
|
|
41
41
|
email:
|
|
42
42
|
- jonathan@gofrias.com
|
|
43
|
-
executables:
|
|
43
|
+
executables:
|
|
44
|
+
- super_auth-editor
|
|
44
45
|
extensions: []
|
|
45
46
|
extra_rdoc_files: []
|
|
46
47
|
files:
|
|
@@ -53,10 +54,8 @@ files:
|
|
|
53
54
|
- README.md
|
|
54
55
|
- Rakefile
|
|
55
56
|
- USAGE.md
|
|
56
|
-
- VISUALIZATION.md
|
|
57
|
-
- app/controllers/super_auth/graph_controller.rb
|
|
58
|
-
- app/views/super_auth/graph/index.html.erb
|
|
59
57
|
- config/routes.rb
|
|
58
|
+
- db/migrate/10_add_super_auth_label_to_resources.rb
|
|
60
59
|
- db/migrate/1_users.rb
|
|
61
60
|
- db/migrate/2_groups.rb
|
|
62
61
|
- db/migrate/3_permissions.rb
|
|
@@ -74,7 +73,9 @@ files:
|
|
|
74
73
|
- db/migrate_activerecord/20250101000006_create_super_auth_edges.rb
|
|
75
74
|
- db/migrate_activerecord/20250101000007_create_super_auth_authorizations.rb
|
|
76
75
|
- db/migrate_activerecord/20250101000009_add_by_current_user_index_to_super_auth_authorizations.rb
|
|
76
|
+
- db/migrate_activerecord/20250101000010_add_super_auth_label_to_super_auth_resources.rb
|
|
77
77
|
- db/seeds/sample_data.rb
|
|
78
|
+
- exe/super_auth-editor
|
|
78
79
|
- lib/basic_loader.rb
|
|
79
80
|
- lib/generators/super_auth/install/install_generator.rb
|
|
80
81
|
- lib/generators/super_auth/install/templates/README
|
|
@@ -93,6 +94,10 @@ files:
|
|
|
93
94
|
- lib/super_auth/active_record/user.rb
|
|
94
95
|
- lib/super_auth/authorization.rb
|
|
95
96
|
- lib/super_auth/edge.rb
|
|
97
|
+
- lib/super_auth/editor.rb
|
|
98
|
+
- lib/super_auth/editor/cli.rb
|
|
99
|
+
- lib/super_auth/editor/index.html
|
|
100
|
+
- lib/super_auth/editor/seed.rb
|
|
96
101
|
- lib/super_auth/group.rb
|
|
97
102
|
- lib/super_auth/nestable.rb
|
|
98
103
|
- lib/super_auth/permission.rb
|
|
@@ -103,8 +108,6 @@ files:
|
|
|
103
108
|
- lib/super_auth/user.rb
|
|
104
109
|
- lib/super_auth/version.rb
|
|
105
110
|
- lib/tasks/super_auth_tasks.rake
|
|
106
|
-
- super_auth.gemspec
|
|
107
|
-
- visualization.html
|
|
108
111
|
homepage: https://github.com/JonathanFrias/super_auth
|
|
109
112
|
licenses:
|
|
110
113
|
- GPL-2.0
|
data/VISUALIZATION.md
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# SuperAuth Graph Visualization
|
|
2
|
-
|
|
3
|
-
SuperAuth includes an interactive graph visualization tool that helps you understand and debug your authorization rules.
|
|
4
|
-
|
|
5
|
-
## Setup
|
|
6
|
-
|
|
7
|
-
### 1. Run the Installer
|
|
8
|
-
|
|
9
|
-
Generate the initializer and install migrations:
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
rails generate super_auth:install
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
This will:
|
|
16
|
-
- Create `config/initializers/super_auth.rb`
|
|
17
|
-
- Install SuperAuth database migrations
|
|
18
|
-
- Show you the next steps
|
|
19
|
-
|
|
20
|
-
### 2. Mount the Engine
|
|
21
|
-
|
|
22
|
-
Add the following to your `config/routes.rb`:
|
|
23
|
-
|
|
24
|
-
```ruby
|
|
25
|
-
Rails.application.routes.draw do
|
|
26
|
-
mount SuperAuth::Engine => '/super_auth'
|
|
27
|
-
|
|
28
|
-
# Your other routes...
|
|
29
|
-
end
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Features
|
|
33
|
-
|
|
34
|
-
### Interactive Graph
|
|
35
|
-
|
|
36
|
-
- **Nodes**: Color-coded by type (Users, Groups, Roles, Permissions, Resources)
|
|
37
|
-
- **Edges**: Solid lines for authorization relationships, dashed for hierarchy
|
|
38
|
-
- **Zoom & Pan**: Navigate large graphs easily
|
|
39
|
-
- **Click nodes**: View node details
|
|
40
|
-
|
|
41
|
-
### Authorization Query
|
|
42
|
-
|
|
43
|
-
1. Select a user from the dropdown
|
|
44
|
-
2. Select a resource from the dropdown
|
|
45
|
-
3. Click "Find Authorization Paths"
|
|
46
|
-
4. View all paths that grant access
|
|
47
|
-
5. See the first path highlighted on the graph
|
|
48
|
-
|
|
49
|
-
### Statistics Panel
|
|
50
|
-
|
|
51
|
-
Real-time counts of:
|
|
52
|
-
- Users
|
|
53
|
-
- Groups (with hierarchical relationships)
|
|
54
|
-
- Roles (with hierarchical relationships)
|
|
55
|
-
- Permissions
|
|
56
|
-
- Resources
|
|
57
|
-
- Authorization edges
|
|
58
|
-
|