super_auth 0.3.3 → 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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +78 -0
  3. data/Gemfile +5 -0
  4. data/Gemfile.lock +8 -1
  5. data/README.md +286 -22
  6. data/USAGE.md +41 -16
  7. data/config/routes.rb +9 -71
  8. data/db/migrate/10_add_super_auth_label_to_resources.rb +13 -0
  9. data/db/migrate/1_users.rb +1 -1
  10. data/db/migrate/5_resources.rb +1 -1
  11. data/db/migrate/7_authorization.rb +2 -2
  12. data/db/migrate/8_add_indexes_to_edges.rb +7 -0
  13. data/db/migrate_activerecord/20250101000001_create_super_auth_users.rb +1 -1
  14. data/db/migrate_activerecord/20250101000005_create_super_auth_resources.rb +1 -1
  15. data/db/migrate_activerecord/20250101000007_create_super_auth_authorizations.rb +2 -2
  16. data/db/migrate_activerecord/20250101000010_add_super_auth_label_to_super_auth_resources.rb +5 -0
  17. data/exe/super_auth-editor +9 -0
  18. data/lib/generators/super_auth/install/templates/README +15 -10
  19. data/lib/generators/super_auth/install/templates/super_auth.rb +16 -5
  20. data/lib/generators/super_auth/rls/rls_generator.rb +22 -0
  21. data/lib/generators/super_auth/rls/templates/migration.rb.erb +15 -0
  22. data/lib/super_auth/active_record/by_current_user.rb +25 -5
  23. data/lib/super_auth/active_record/resource.rb +41 -0
  24. data/lib/super_auth/active_record/user.rb +3 -1
  25. data/lib/super_auth/authorization.rb +12 -0
  26. data/lib/super_auth/edge.rb +65 -100
  27. data/lib/super_auth/editor/cli.rb +91 -0
  28. data/lib/super_auth/editor/index.html +423 -0
  29. data/lib/super_auth/editor/seed.rb +170 -0
  30. data/lib/super_auth/editor.rb +273 -0
  31. data/lib/super_auth/nestable.rb +41 -3
  32. data/lib/super_auth/railtie.rb +0 -2
  33. data/lib/super_auth/rls.rb +256 -0
  34. data/lib/super_auth/user.rb +3 -1
  35. data/lib/super_auth/version.rb +1 -1
  36. data/lib/super_auth.rb +85 -0
  37. data/lib/tasks/super_auth_tasks.rake +28 -0
  38. metadata +15 -9
  39. data/VISUALIZATION.md +0 -58
  40. data/app/controllers/super_auth/graph_controller.rb +0 -654
  41. data/app/views/super_auth/graph/index.html.erb +0 -1408
  42. data/super_auth.gemspec +0 -35
  43. data/visualization.html +0 -747
data/lib/super_auth.rb CHANGED
@@ -22,6 +22,43 @@ module SuperAuth
22
22
  @missing_user_behavior = behavior
23
23
  end
24
24
 
25
+ # Column type for the external id columns (users.external_id,
26
+ # resources.external_id and their copies on authorizations). Set it to your
27
+ # application's primary key type (:bigint, :uuid, :string, ...) BEFORE
28
+ # running the super_auth migrations — the columns are then created with the
29
+ # matching type and every comparison against your tables' pks is natively
30
+ # typed, with no casting anywhere. Default :string.
31
+ def self.external_id_type
32
+ @external_id_type || :string
33
+ end
34
+
35
+ def self.external_id_type=(type)
36
+ @external_id_type = type
37
+ end
38
+
39
+ # Sequel migrations take the Ruby String class for varchar; anything else
40
+ # passes through as the literal database type name.
41
+ def self.sequel_external_id_type
42
+ external_id_type == :string ? String : external_id_type
43
+ end
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
+
25
62
  def self.load
26
63
  require "super_auth/authorization"
27
64
  require "super_auth/edge"
@@ -30,6 +67,7 @@ module SuperAuth
30
67
  require "super_auth/permission"
31
68
  require "super_auth/railtie"
32
69
  require "super_auth/resource"
70
+ require "super_auth/rls"
33
71
  require "super_auth/role"
34
72
  require "super_auth/user"
35
73
  require "super_auth/active_record" if defined?(ActiveRecord::Base)
@@ -40,6 +78,25 @@ module SuperAuth
40
78
  require "pathname"
41
79
  path = Pathname.new(__FILE__).parent.parent.join("db", "migrate")
42
80
  Sequel::Migrator.run(SuperAuth.db, path)
81
+ refresh_model_schemas
82
+ end
83
+
84
+ # Both ORMs cache column types per model class; after (re)installing the
85
+ # migrations those caches can describe a previous schema (e.g. a different
86
+ # external_id_type) and silently miscast assigned values.
87
+ def self.refresh_model_schemas
88
+ models = %w[User Group Permission Role Resource Edge Authorization]
89
+ if defined?(SuperAuth::ActiveRecord::User)
90
+ models.each do |name|
91
+ SuperAuth::ActiveRecord.const_get(name).reset_column_information
92
+ end
93
+ end
94
+ if defined?(SuperAuth::User) && SuperAuth::User.respond_to?(:set_dataset)
95
+ models.each do |name|
96
+ model = SuperAuth.const_get(name)
97
+ model.set_dataset(model.dataset)
98
+ end
99
+ end
43
100
  end
44
101
 
45
102
  def self.uninstall_migrations
@@ -55,6 +112,33 @@ module SuperAuth
55
112
  raise Error, "Failed to uninstall migrations: #{e.message}"
56
113
  end
57
114
 
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))
140
+ end
141
+
58
142
  def self.current_user=(user)
59
143
  Thread.current[:super_auth_current_user] = user
60
144
  end
@@ -128,4 +212,5 @@ module SuperAuth
128
212
  end
129
213
  end
130
214
 
215
+ require "super_auth/rls"
131
216
  require "super_auth/railtie" if defined?(Rails::Railtie)
@@ -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.3.3
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Frias
8
- bindir: bin
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,11 +73,15 @@ 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
81
82
  - lib/generators/super_auth/install/templates/super_auth.rb
83
+ - lib/generators/super_auth/rls/rls_generator.rb
84
+ - lib/generators/super_auth/rls/templates/migration.rb.erb
82
85
  - lib/super_auth.rb
83
86
  - lib/super_auth/active_record.rb
84
87
  - lib/super_auth/active_record/authorization.rb
@@ -91,20 +94,23 @@ files:
91
94
  - lib/super_auth/active_record/user.rb
92
95
  - lib/super_auth/authorization.rb
93
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
94
101
  - lib/super_auth/group.rb
95
102
  - lib/super_auth/nestable.rb
96
103
  - lib/super_auth/permission.rb
97
104
  - lib/super_auth/railtie.rb
98
105
  - lib/super_auth/resource.rb
106
+ - lib/super_auth/rls.rb
99
107
  - lib/super_auth/role.rb
100
108
  - lib/super_auth/user.rb
101
109
  - lib/super_auth/version.rb
102
110
  - lib/tasks/super_auth_tasks.rake
103
- - super_auth.gemspec
104
- - visualization.html
105
111
  homepage: https://github.com/JonathanFrias/super_auth
106
112
  licenses:
107
- - MIT
113
+ - GPL-2.0
108
114
  metadata:
109
115
  homepage_uri: https://github.com/JonathanFrias/super_auth
110
116
  source_code_uri: https://github.com/JonathanFrias/super_auth
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
-