super_auth 0.1.5 → 0.3.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/Gemfile +3 -7
  4. data/Gemfile.lock +25 -14
  5. data/LICENSE.txt +125 -21
  6. data/README.md +32 -1
  7. data/Rakefile +0 -2
  8. data/USAGE.md +619 -0
  9. data/VISUALIZATION.md +58 -0
  10. data/app/controllers/super_auth/graph_controller.rb +661 -0
  11. data/app/views/super_auth/graph/index.html.erb +1408 -0
  12. data/config/routes.rb +73 -0
  13. data/db/migrate/1_users.rb +1 -0
  14. data/db/migrate/2_groups.rb +8 -1
  15. data/db/migrate/4_roles.rb +8 -1
  16. data/db/migrate/5_resources.rb +1 -0
  17. data/db/migrate/7_authorization.rb +2 -0
  18. data/db/migrate/8_add_indexes_to_edges.rb +17 -0
  19. data/db/migrate/9_add_by_current_user_index.rb +12 -0
  20. data/db/migrate_activerecord/20250101000001_create_super_auth_users.rb +10 -0
  21. data/db/migrate_activerecord/20250101000002_create_super_auth_groups.rb +11 -0
  22. data/db/migrate_activerecord/20250101000003_create_super_auth_permissions.rb +8 -0
  23. data/db/migrate_activerecord/20250101000004_create_super_auth_roles.rb +11 -0
  24. data/db/migrate_activerecord/20250101000005_create_super_auth_resources.rb +10 -0
  25. data/db/migrate_activerecord/20250101000006_create_super_auth_edges.rb +12 -0
  26. data/db/migrate_activerecord/20250101000007_create_super_auth_authorizations.rb +41 -0
  27. data/db/migrate_activerecord/20250101000009_add_by_current_user_index_to_super_auth_authorizations.rb +7 -0
  28. data/db/seeds/sample_data.rb +193 -0
  29. data/lib/basic_loader.rb +0 -2
  30. data/lib/generators/super_auth/install/install_generator.rb +19 -0
  31. data/lib/generators/super_auth/install/templates/README +29 -0
  32. data/lib/generators/super_auth/install/templates/super_auth.rb +7 -0
  33. data/lib/super_auth/active_record/by_current_user.rb +26 -11
  34. data/lib/super_auth/active_record/edge.rb +45 -0
  35. data/lib/super_auth/active_record/group.rb +7 -0
  36. data/lib/super_auth/active_record/permission.rb +4 -0
  37. data/lib/super_auth/active_record/resource.rb +1 -0
  38. data/lib/super_auth/active_record/role.rb +7 -0
  39. data/lib/super_auth/active_record/user.rb +6 -0
  40. data/lib/super_auth/active_record.rb +17 -0
  41. data/lib/super_auth/edge.rb +190 -131
  42. data/lib/super_auth/group.rb +1 -0
  43. data/lib/super_auth/nestable.rb +17 -10
  44. data/lib/super_auth/permission.rb +1 -1
  45. data/lib/super_auth/railtie.rb +26 -25
  46. data/lib/super_auth/role.rb +2 -1
  47. data/lib/super_auth/user.rb +8 -8
  48. data/lib/super_auth/version.rb +1 -3
  49. data/lib/super_auth.rb +72 -40
  50. data/super_auth.gemspec +35 -0
  51. data/visualization.html +747 -0
  52. metadata +24 -6
@@ -1,3 +1,7 @@
1
1
  class SuperAuth::ActiveRecord::Permission < ActiveRecord::Base
2
2
  self.table_name = 'super_auth_permissions'
3
+
4
+ has_many :edges, class_name: 'SuperAuth::ActiveRecord::Edge'
5
+ scope :with_edges, -> { joins(:edges) }
6
+ scope :with_roles, -> { from(%Q[(#{SuperAuth::Permission.with_roles.sql}) as super_auth_permissions]) }
3
7
  end
@@ -1,3 +1,4 @@
1
1
  class SuperAuth::ActiveRecord::Resource < ActiveRecord::Base
2
2
  self.table_name = 'super_auth_resources'
3
+ belongs_to :external, polymorphic: true, optional: true
3
4
  end
@@ -1,3 +1,10 @@
1
1
  class SuperAuth::ActiveRecord::Role < ActiveRecord::Base
2
2
  self.table_name = 'super_auth_roles'
3
+
4
+ belongs_to :parent, class_name: 'SuperAuth::ActiveRecord::Role', optional: true
5
+
6
+ def descendants_dataset
7
+ sql = SuperAuth::Role.new(id: self.id, parent_id: self.parent_id).descendants_dataset.sql
8
+ self.class.from(%Q[(#{sql}) as super_auth_roles])
9
+ end
3
10
  end
@@ -1,8 +1,14 @@
1
1
  class SuperAuth::ActiveRecord::User < ActiveRecord::Base
2
2
  self.table_name = 'super_auth_users'
3
3
 
4
+ belongs_to :external, polymorphic: true, optional: true
5
+
4
6
  def model_name = ActiveModel::Name.new(:user)
5
7
 
6
8
  def system? = self.class.system == self
7
9
  def self.system = find_or_create_by(name: "system")
10
+
11
+ has_many :edges, class_name: 'SuperAuth::ActiveRecord::Edge'
12
+ scope :with_edges, -> { joins(:edges) }
13
+ scope :with_groups, -> { from(%Q[(#{SuperAuth::User.with_groups.sql}) as super_auth_users]) }
8
14
  end
@@ -1,3 +1,20 @@
1
1
  require "active_record"
2
2
  module SuperAuth::ActiveRecord
3
3
  end
4
+
5
+ class ActiveRecord::Base
6
+ class << self
7
+ def super_auth
8
+ include SuperAuth::ActiveRecord::ByCurrentUser
9
+ end
10
+ end
11
+ end
12
+
13
+ require "super_auth/active_record/authorization"
14
+ require "super_auth/active_record/by_current_user"
15
+ require "super_auth/active_record/edge"
16
+ require "super_auth/active_record/group"
17
+ require "super_auth/active_record/permission"
18
+ require "super_auth/active_record/resource"
19
+ require "super_auth/active_record/role"
20
+ require "super_auth/active_record/user"
@@ -1,25 +1,30 @@
1
1
  class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
2
+ plugin :dirty
3
+
2
4
  many_to_one :user
3
5
  many_to_one :group
4
6
  many_to_one :permission
5
7
  many_to_one :role
6
8
  many_to_one :resource
7
9
 
8
- def before_save
9
- @affected_users = SuperAuth::Authorization.where(user_id: user_id).distinct.select_map(:user_id) + [user_id]
10
- end
11
-
12
- def after_save
13
- SuperAuth::Authorization.db.transaction do
14
- SuperAuth::Authorization.where(user_id: @affected_users).delete
15
- SuperAuth::Authorization.multi_insert(
16
- SuperAuth::Edge.authorizations.where(user_id: @affected_users)
17
- .to_a
18
- )
10
+ class << self
11
+ def string_cast_type
12
+ case SuperAuth.db.database_type
13
+ when :mysql, :mysql2
14
+ :char
15
+ else
16
+ :text
17
+ end
19
18
  end
20
- end
21
19
 
22
- class << self
20
+ def integer_cast_type
21
+ case SuperAuth.db.database_type
22
+ when :mysql, :mysql2
23
+ :signed
24
+ else
25
+ :bigint
26
+ end
27
+ end
23
28
 
24
29
  def authorizations
25
30
  users_groups_roles_permissions_resources
@@ -30,10 +35,15 @@ class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
30
35
  end
31
36
 
32
37
  def users_groups_roles_permissions_resources
33
- users_groups_roles_ds = SuperAuth::User.join(:super_auth_edges, user_id: :id).select_all(:super_auth_users).join(SuperAuth::Group.from(SuperAuth::Group.trees).as(:groups), id: :group_id).select(
38
+ cast_type = string_cast_type
39
+ users_groups_roles_ds = SuperAuth::User.join(:super_auth_edges, user_id: :id).
40
+ select_all(:super_auth_users).
41
+ join(SuperAuth::Group.from(SuperAuth::Group.trees).as(:groups), Sequel.function(:concat, ',', Sequel[:groups][:group_path], ',').like(Sequel.function(:concat, '%,', Sequel[:groups][:id], ',%'))).
42
+ select(
34
43
  Sequel[:super_auth_users][:id].as(:user_id),
35
44
  Sequel[:super_auth_users][:name].as(:user_name),
36
45
  Sequel[:super_auth_users][:external_id].as(:user_external_id),
46
+ Sequel[:super_auth_users][:external_type].as(:user_external_type),
37
47
  Sequel[:super_auth_users][:created_at].as(:user_created_at),
38
48
  Sequel[:super_auth_users][:updated_at].as(:user_updated_at),
39
49
  Sequel[:groups][:id].as(:group_id),
@@ -46,8 +56,8 @@ class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
46
56
  Sequel[:groups][:group_path],
47
57
  Sequel[:groups][:group_name_path],
48
58
  Sequel[:groups][:parent_id],
49
- Sequel[:groups][:created_at].cast(:text).as(:group_created_at),
50
- Sequel[:groups][:updated_at].cast(:text).as(:group_updated_at),
59
+ Sequel[:groups][:created_at].cast(cast_type).as(:group_created_at),
60
+ Sequel[:groups][:updated_at].cast(cast_type).as(:group_updated_at),
51
61
  ).join(Sequel[:super_auth_edges].as(:group_role_edges), Sequel[:group_role_edges][:group_id] => Sequel[:groups][:id]).select_append(
52
62
  Sequel[:group_role_edges][:id].as(:group_role_edge_id),
53
63
  Sequel[:group_role_edges][:permission_id].as(:group_role_edge_permission_id),
@@ -58,7 +68,7 @@ class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
58
68
 
59
69
  SuperAuth::Edge.from(
60
70
  SuperAuth::Edge.from(
61
- SuperAuth::Group.cte(SuperAuth::Group.where(id: users_groups_roles_ds.select(Sequel[:groups][:id])).select(:id)).select { [id.as(:group_id), name.as(:group_name), parent_id.as(:group_parent_id), group_path, group_name_path, created_at.cast(:text).as(:group_created_at), updated_at.as(:group_updated_at)] },
71
+ SuperAuth::Group.cte(SuperAuth::Group.where(id: users_groups_roles_ds.select(Sequel[:groups][:id])).select(:id)).select { [id.as(:group_id), name.as(:group_name), parent_id.as(:group_parent_id), group_path, group_name_path, created_at.cast(cast_type).as(:group_created_at), updated_at.as(:group_updated_at)] },
62
72
  SuperAuth::Role.cte(users_groups_roles_ds.select(Sequel[:group_role_edges][:role_id])).select { [id.as(:role_id), name.as(:role_name), parent_id.as(:role_parent_id), role_path, role_name_path, created_at.as(:role_created_at), updated_at.as(:role_updated_at) ] }
63
73
  ).as(:users_groups_roles_permissions_resources)
64
74
  ).join(Sequel[:super_auth_edges].as(:user_edges), Sequel[:user_edges][:group_id] => Sequel[:users_groups_roles_permissions_resources][:group_id])
@@ -67,33 +77,35 @@ class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
67
77
  Sequel[:super_auth_users][:id].as(:user_id),
68
78
  Sequel[:super_auth_users][:name].as(:user_name),
69
79
  Sequel[:super_auth_users][:external_id].as(:user_external_id),
70
- Sequel[:super_auth_users][:created_at].cast(:text).as(:user_created_at),
71
- Sequel[:super_auth_users][:updated_at].cast(:text).as(:user_updated_at),
80
+ Sequel[:super_auth_users][:external_type].as(:user_external_type),
81
+ Sequel[:super_auth_users][:created_at].cast(cast_type).as(:user_created_at),
82
+ Sequel[:super_auth_users][:updated_at].cast(cast_type).as(:user_updated_at),
72
83
 
73
84
  Sequel[:users_groups_roles_permissions_resources][:group_id],
74
85
  Sequel[:users_groups_roles_permissions_resources][:group_name],
75
86
  Sequel[:users_groups_roles_permissions_resources][:group_path],
76
87
  Sequel[:users_groups_roles_permissions_resources][:group_name_path],
77
88
  Sequel[:users_groups_roles_permissions_resources][:group_parent_id],
78
- Sequel[:users_groups_roles_permissions_resources][:group_created_at].cast(:text).as(:group_created_at),
79
- Sequel[:users_groups_roles_permissions_resources][:group_updated_at].cast(:text).as(:group_updated_at),
89
+ Sequel[:users_groups_roles_permissions_resources][:group_created_at].cast(cast_type).as(:group_created_at),
90
+ Sequel[:users_groups_roles_permissions_resources][:group_updated_at].cast(cast_type).as(:group_updated_at),
80
91
 
81
92
  Sequel[:users_groups_roles_permissions_resources][:role_id],
82
93
  Sequel[:users_groups_roles_permissions_resources][:role_name],
83
94
  Sequel[:users_groups_roles_permissions_resources][:role_path],
84
95
  Sequel[:users_groups_roles_permissions_resources][:role_name_path],
85
96
  Sequel[:users_groups_roles_permissions_resources][:role_parent_id],
86
- Sequel[:users_groups_roles_permissions_resources][:role_created_at].cast(:text).as(:role_created_at),
87
- Sequel[:users_groups_roles_permissions_resources][:role_updated_at].cast(:text).as(:role_updated_at),
97
+ Sequel[:users_groups_roles_permissions_resources][:role_created_at].cast(cast_type).as(:role_created_at),
98
+ Sequel[:users_groups_roles_permissions_resources][:role_updated_at].cast(cast_type).as(:role_updated_at),
88
99
 
89
100
  Sequel[:super_auth_permissions][:id].as(:permission_id),
90
101
  Sequel[:super_auth_permissions][:name].as(:permission_name),
91
- Sequel[:super_auth_permissions][:created_at].cast(:text).as(:permission_created_at),
92
- Sequel[:super_auth_permissions][:updated_at].cast(:text).as(:permission_updated_at),
102
+ Sequel[:super_auth_permissions][:created_at].cast(cast_type).as(:permission_created_at),
103
+ Sequel[:super_auth_permissions][:updated_at].cast(cast_type).as(:permission_updated_at),
93
104
 
94
105
  Sequel[:super_auth_resources][:id].as(:resource_id),
95
106
  Sequel[:super_auth_resources][:name].as(:resource_name),
96
- Sequel[:super_auth_resources][:external_id].as(:resource_external_id)
107
+ Sequel[:super_auth_resources][:external_id].as(:resource_external_id),
108
+ Sequel[:super_auth_resources][:external_type].as(:resource_external_type)
97
109
  )
98
110
  .join(Sequel[:super_auth_edges].as(:permission_edges), Sequel[:permission_edges][:role_id] => Sequel[:users_groups_roles_permissions_resources][:role_id])
99
111
  .join(Sequel[:super_auth_permissions], id: Sequel[:permission_edges][:permission_id])
@@ -103,85 +115,117 @@ class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
103
115
  end
104
116
 
105
117
  def users_groups_permissions_resources
106
- SuperAuth::User.
118
+ cast_type = string_cast_type
119
+ # Join users to their group via edges, then to the group CTE to get the user's group_path.
120
+ # Use group_path to find all ancestor groups (any group whose id appears in the user's group_path).
121
+ # Then join permission edges on those ancestor groups.
122
+ SuperAuth::User.db[:super_auth_users].
107
123
  join(Sequel[:super_auth_edges].as(:user_edges), user_id: :id).
108
- join(SuperAuth::Group.from(SuperAuth::Group.trees).as(:groups), id: :group_id).
124
+ join(SuperAuth::Group.from(SuperAuth::Group.trees).as(:user_groups), Sequel[:user_groups][:id] => Sequel[:user_edges][:group_id]).
125
+ join(Sequel[:super_auth_edges].as(:group_edges),
126
+ Sequel.function(:concat, ',', Sequel[:user_groups][:group_path], ',').like(
127
+ Sequel.function(:concat, '%,', Sequel[:group_edges][:group_id].cast(cast_type), ',%')
128
+ )
129
+ ).
130
+ join(Sequel[:super_auth_permissions], id: Sequel[:group_edges][:permission_id]).
131
+ join(Sequel[:super_auth_edges].as(:permission_edges), Sequel[:permission_edges][:permission_id] => Sequel[:super_auth_permissions][:id]).
132
+ join(Sequel[:super_auth_resources], id: Sequel[:permission_edges][:resource_id]).
109
133
  select(
110
134
  Sequel[:super_auth_users][:id].as(:user_id),
111
135
  Sequel[:super_auth_users][:name].as(:user_name),
112
136
  Sequel[:super_auth_users][:external_id].as(:user_external_id),
113
- Sequel[:super_auth_users][:created_at].cast(:text).as(:user_created_at),
114
- Sequel[:super_auth_users][:updated_at].cast(:text).as(:user_updated_at),
115
-
116
- Sequel[:groups][:id].as(:group_id),
117
- Sequel[:groups][:name].as(:group_name),
118
- Sequel[:groups][:group_path],
119
- Sequel[:groups][:group_name_path],
120
- Sequel[:groups][:parent_id].as(:group_parent_id),
121
- Sequel[:groups][:created_at].cast(:text).as(:group_created_at),
122
- Sequel[:groups][:updated_at].cast(:text).as(:group_updated_at),
123
-
124
- Sequel.lit(%[0 as "role_id"]), # Sequel[:roles][:id].as(:role_id),
125
- Sequel::NULL.as(:role_name), # Sequel[:roles][:name].as(:role_name),
126
- Sequel::NULL.as(:role_path), # Sequel[:roles][:role_path],
127
- Sequel::NULL.as(:role_name_path), # Sequel[:roles][:role_name_path].as(:role_name_path),
128
- Sequel::lit(%Q[0 as "role_parent_id"]), # Sequel[:roles][:parent_id].as(:role_parent_id),
129
- Sequel::NULL.as(:role_created_at), # Sequel[:roles][:created_at].as(:role_created_at),
130
- Sequel::NULL.as(:role_updated_at), # Sequel[:roles][:updated_at].as(:role_updated_at),
137
+ Sequel[:super_auth_users][:external_type].as(:user_external_type),
138
+ Sequel[:super_auth_users][:created_at].cast(cast_type).as(:user_created_at),
139
+ Sequel[:super_auth_users][:updated_at].cast(cast_type).as(:user_updated_at),
140
+
141
+ Sequel[:user_groups][:id].as(:group_id),
142
+ Sequel[:user_groups][:name].as(:group_name),
143
+ Sequel[:user_groups][:group_path],
144
+ Sequel[:user_groups][:group_name_path],
145
+ Sequel[:user_groups][:parent_id].as(:group_parent_id),
146
+ Sequel[:user_groups][:created_at].cast(cast_type).as(:group_created_at),
147
+ Sequel[:user_groups][:updated_at].cast(cast_type).as(:group_updated_at),
148
+
149
+ Sequel.cast(nil, integer_cast_type).as(:role_id),
150
+ Sequel.cast(nil, string_cast_type).as(:role_name),
151
+ Sequel.cast(nil, string_cast_type).as(:role_path),
152
+ Sequel.cast(nil, string_cast_type).as(:role_name_path),
153
+ Sequel.cast(nil, integer_cast_type).as(:role_parent_id),
154
+ Sequel.cast(nil, string_cast_type).as(:role_created_at),
155
+ Sequel.cast(nil, string_cast_type).as(:role_updated_at),
131
156
 
132
157
  Sequel[:super_auth_permissions][:id].as(:permission_id),
133
158
  Sequel[:super_auth_permissions][:name].as(:permission_name),
134
- Sequel[:super_auth_permissions][:created_at].cast(:text).as(:permission_created_at),
135
- Sequel[:super_auth_permissions][:updated_at].cast(:text).as(:permission_updated_at),
159
+ Sequel[:super_auth_permissions][:created_at].cast(cast_type).as(:permission_created_at),
160
+ Sequel[:super_auth_permissions][:updated_at].cast(cast_type).as(:permission_updated_at),
136
161
 
137
162
  Sequel[:super_auth_resources][:id].as(:resource_id),
138
163
  Sequel[:super_auth_resources][:name].as(:resource_name),
139
164
  Sequel[:super_auth_resources][:external_id].as(:resource_external_id),
165
+ Sequel[:super_auth_resources][:external_type].as(:resource_external_type),
140
166
  ).
141
- join(Sequel[:super_auth_edges].as(:permission_edges), Sequel[:permission_edges][:group_id] => Sequel[:groups][:id]).
142
- join(Sequel[:super_auth_permissions], id: Sequel[:permission_edges][:permission_id]).
143
- join(Sequel[:super_auth_edges].as(:resource_edges), Sequel[:resource_edges][:permission_id] => Sequel[:super_auth_permissions][:id]).
144
- join(Sequel[:super_auth_resources], id: Sequel[:resource_edges][:resource_id]).
145
167
  distinct
146
168
  end
147
169
 
148
170
  def users_roles_permissions_resources
149
- SuperAuth::User.
150
- join(Sequel[:super_auth_edges].as(:user_edges), user_id: :id).
151
- join(SuperAuth::Role.from(SuperAuth::Role.trees).as(:roles), id: :role_id).
152
- select(
153
- Sequel[:super_auth_users][:id].as(:user_id),
154
- Sequel[:super_auth_users][:name].as(:user_name),
155
- Sequel[:super_auth_users][:external_id].as(:user_external_id),
156
- Sequel[:super_auth_users][:created_at].cast(:text).as(:user_created_at),
157
- Sequel[:super_auth_users][:updated_at].cast(:text).as(:user_updated_at),
158
-
159
- Sequel.lit(%Q[0 as "group_id"]), # Sequel[:super_auth_groups][:group_id],
160
- Sequel::NULL.as(:group_name), # Sequel[:super_auth_groups][:group_name],
161
- Sequel::NULL.as(:group_path), # Sequel[:super_auth_groups][:group_path],
162
- Sequel::NULL.as(:group_name_path), # Sequel[:super_auth_groups][:group_name_path],
163
- Sequel.lit(%Q[0 as "group_parent_id"]), # Sequel[:super_auth_groups][:group_parent_id],
164
- Sequel.lit(%Q['1970-01-01 00:00:00.000000-00' as "group_created_at"]), # Sequel[:super_auth_groups][:group_created_at],
165
- Sequel.lit(%Q['1970-01-01 00:00:00.000000-00' as "group_updated_at"]), # Sequel[:super_auth_groups][:group_updated_at],
166
-
167
- Sequel[:roles][:id].as(:role_id),
168
- Sequel[:roles][:name].as(:role_name),
169
- Sequel[:roles][:role_path],
170
- Sequel[:roles][:role_name_path].as(:role_name_path),
171
- Sequel[:roles][:parent_id].as(:role_parent_id),
172
- Sequel[:roles][:created_at].cast(:text).as(:role_created_at),
173
- Sequel[:roles][:updated_at].cast(:text).as(:role_updated_at),
171
+ cast_type = string_cast_type
174
172
 
175
- Sequel[:super_auth_permissions][:id].as(:permission_id),
176
- Sequel[:super_auth_permissions][:name].as(:permission_name),
177
- Sequel[:super_auth_permissions][:created_at].cast(:text).as(:permission_created_at),
178
- Sequel[:super_auth_permissions][:updated_at].cast(:text).as(:permission_updated_at),
173
+ # Step 1: Find which roles users are directly linked to via edges
174
+ user_role_ids_ds = SuperAuth::Edge.where(Sequel.~(user_id: nil) & Sequel.~(role_id: nil)).select(:role_id)
179
175
 
180
- Sequel[:super_auth_resources][:id].as(:resource_id),
181
- Sequel[:super_auth_resources][:name].as(:resource_name),
182
- Sequel[:super_auth_resources][:external_id].as(:resource_external_id),
176
+ # Step 2: Expand those roles to all descendants via CTE
177
+ role_cte = SuperAuth::Role.cte(user_role_ids_ds).select {
178
+ [id.as(:role_id), name.as(:role_name), parent_id.as(:role_parent_id), role_path, role_name_path, created_at.as(:role_created_at), updated_at.as(:role_updated_at)]
179
+ }
180
+
181
+ # Step 3: Build the query from the expanded role tree
182
+ SuperAuth::Edge.from(role_cte.as(:users_roles_permissions_resources)).
183
+ # Join user_edges — match users who link to any role in the expanded CTE
184
+ # The user's edge links to an ancestor role, but the CTE path contains that ancestor
185
+ # We use the role_path to check: the role_path of the CTE row starts with the user's linked role
186
+ join(Sequel[:super_auth_edges].as(:user_edges),
187
+ Sequel.function(:concat, ',', Sequel[:users_roles_permissions_resources][:role_path], ',').like(
188
+ Sequel.function(:concat, '%,', Sequel[:user_edges][:role_id].cast(cast_type), ',%')
189
+ )
183
190
  ).
184
- join(Sequel[:super_auth_edges].as(:permission_edges), Sequel[:permission_edges][:role_id] => Sequel[:roles][:id]).
191
+ where(Sequel.~(Sequel[:user_edges][:user_id] => nil) & Sequel.~(Sequel[:user_edges][:role_id] => nil)).
192
+ join(Sequel[:super_auth_users], id: Sequel[:user_edges][:user_id]).
193
+ select(
194
+ Sequel[:super_auth_users][:id].as(:user_id),
195
+ Sequel[:super_auth_users][:name].as(:user_name),
196
+ Sequel[:super_auth_users][:external_id].as(:user_external_id),
197
+ Sequel[:super_auth_users][:external_type].as(:user_external_type),
198
+ Sequel[:super_auth_users][:created_at].cast(cast_type).as(:user_created_at),
199
+ Sequel[:super_auth_users][:updated_at].cast(cast_type).as(:user_updated_at),
200
+
201
+ Sequel.cast(nil, integer_cast_type).as(:group_id),
202
+ Sequel.cast(nil, string_cast_type).as(:group_name),
203
+ Sequel.cast(nil, string_cast_type).as(:group_path),
204
+ Sequel.cast(nil, string_cast_type).as(:group_name_path),
205
+ Sequel.cast(nil, integer_cast_type).as(:group_parent_id),
206
+ Sequel.cast(nil, string_cast_type).as(:group_created_at),
207
+ Sequel.cast(nil, string_cast_type).as(:group_updated_at),
208
+
209
+ Sequel[:users_roles_permissions_resources][:role_id],
210
+ Sequel[:users_roles_permissions_resources][:role_name],
211
+ Sequel[:users_roles_permissions_resources][:role_path],
212
+ Sequel[:users_roles_permissions_resources][:role_name_path],
213
+ Sequel[:users_roles_permissions_resources][:role_parent_id],
214
+ Sequel[:users_roles_permissions_resources][:role_created_at].cast(cast_type).as(:role_created_at),
215
+ Sequel[:users_roles_permissions_resources][:role_updated_at].cast(cast_type).as(:role_updated_at),
216
+
217
+ Sequel[:super_auth_permissions][:id].as(:permission_id),
218
+ Sequel[:super_auth_permissions][:name].as(:permission_name),
219
+ Sequel[:super_auth_permissions][:created_at].cast(cast_type).as(:permission_created_at),
220
+ Sequel[:super_auth_permissions][:updated_at].cast(cast_type).as(:permission_updated_at),
221
+
222
+ Sequel[:super_auth_resources][:id].as(:resource_id),
223
+ Sequel[:super_auth_resources][:name].as(:resource_name),
224
+ Sequel[:super_auth_resources][:external_id].as(:resource_external_id),
225
+ Sequel[:super_auth_resources][:external_type].as(:resource_external_type),
226
+ ).
227
+ # Join permission and resource edges on the expanded role
228
+ join(Sequel[:super_auth_edges].as(:permission_edges), Sequel[:permission_edges][:role_id] => Sequel[:users_roles_permissions_resources][:role_id]).
185
229
  join(Sequel[:super_auth_permissions], id: Sequel[:permission_edges][:permission_id]).
186
230
  join(Sequel[:super_auth_edges].as(:resource_edges), Sequel[:resource_edges][:permission_id] => Sequel[:super_auth_permissions][:id]).
187
231
  join(Sequel[:super_auth_resources], id: Sequel[:resource_edges][:resource_id]).
@@ -189,39 +233,42 @@ class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
189
233
  end
190
234
 
191
235
  def users_permissions_resources
236
+ cast_type = string_cast_type
192
237
  SuperAuth::User.
193
238
  join(Sequel[:super_auth_edges].as(:user_edges), user_id: :id).
194
239
  select(
195
240
  Sequel[:super_auth_users][:id].as(:user_id),
196
241
  Sequel[:super_auth_users][:name].as(:user_name),
197
242
  Sequel[:super_auth_users][:external_id].as(:user_external_id),
198
- Sequel[:super_auth_users][:created_at].cast(:text).as(:user_created_at),
199
- Sequel[:super_auth_users][:updated_at].cast(:text).as(:user_updated_at),
200
-
201
- Sequel.lit(%Q[0 as "group_id"]), # Sequel[:groups][:group_id],
202
- Sequel::NULL.as(:group_name), # Sequel[:groups][:group_name],
203
- Sequel::NULL.as(:group_path), # Sequel[:groups][:group_path],
204
- Sequel::NULL.as(:group_name_path), # Sequel[:groups][:group_name_path],
205
- Sequel.lit(%Q[0 as "group_parent_id"]), # Sequel[:groups][:group_id],
206
- Sequel.lit(%Q['1970-01-01 00:00:00.000000-00' as "group_created_at"]), # Sequel[:groups][:group_created_at],
207
- Sequel.lit(%Q['1970-01-01 00:00:00.000000-00' as "group_updated_at"]), # Sequel[:groups][:group_updated_at],
208
-
209
- Sequel.lit(%Q[0 as "role_id"]), # Sequel[:roles][:role_id],
210
- Sequel::NULL.as(:role_name), # Sequel[:roles][:role_name],
211
- Sequel::NULL.as(:role_path), # Sequel[:roles][:role_path],
212
- Sequel::NULL.as(:role_name_path), # Sequel[:roles][:role_name_path],
213
- Sequel.lit(%Q[0 as "role_parent_id"]), # Sequel[:roles][:role_parent_id],
214
- Sequel::NULL.as(:role_created_at), # Sequel[:roles][:role_created_at],
215
- Sequel::NULL.as(:role_updated_at), # Sequel[:roles][:role_updated_at],
243
+ Sequel[:super_auth_users][:external_type].as(:user_external_type),
244
+ Sequel[:super_auth_users][:created_at].cast(cast_type).as(:user_created_at),
245
+ Sequel[:super_auth_users][:updated_at].cast(cast_type).as(:user_updated_at),
246
+
247
+ Sequel.cast(nil, integer_cast_type).as(:group_id),
248
+ Sequel.cast(nil, string_cast_type).as(:group_name),
249
+ Sequel.cast(nil, string_cast_type).as(:group_path),
250
+ Sequel.cast(nil, string_cast_type).as(:group_name_path),
251
+ Sequel.cast(nil, integer_cast_type).as(:group_parent_id),
252
+ Sequel.cast(nil, string_cast_type).as(:group_created_at),
253
+ Sequel.cast(nil, string_cast_type).as(:group_updated_at),
254
+
255
+ Sequel.cast(nil, integer_cast_type).as(:role_id),
256
+ Sequel.cast(nil, string_cast_type).as(:role_name),
257
+ Sequel.cast(nil, string_cast_type).as(:role_path),
258
+ Sequel.cast(nil, string_cast_type).as(:role_name_path),
259
+ Sequel.cast(nil, integer_cast_type).as(:role_parent_id),
260
+ Sequel.cast(nil, string_cast_type).as(:role_created_at),
261
+ Sequel.cast(nil, string_cast_type).as(:role_updated_at),
216
262
 
217
263
  Sequel[:super_auth_permissions][:id].as(:permission_id),
218
264
  Sequel[:super_auth_permissions][:name].as(:permission_name),
219
- Sequel[:super_auth_permissions][:created_at].cast(:text).as(:permission_created_at),
220
- Sequel[:super_auth_permissions][:updated_at].cast(:text).as(:permission_updated_at),
265
+ Sequel[:super_auth_permissions][:created_at].cast(cast_type).as(:permission_created_at),
266
+ Sequel[:super_auth_permissions][:updated_at].cast(cast_type).as(:permission_updated_at),
221
267
 
222
268
  Sequel[:super_auth_resources][:id].as(:resource_id),
223
269
  Sequel[:super_auth_resources][:name].as(:resource_name),
224
- Sequel[:super_auth_resources][:external_id].as(:resource_external_id)
270
+ Sequel[:super_auth_resources][:external_id].as(:resource_external_id),
271
+ Sequel[:super_auth_resources][:external_type].as(:resource_external_type)
225
272
  ).
226
273
  join(Sequel[:super_auth_edges].as(:permission_edges), Sequel[:permission_edges][:user_id] => Sequel[:super_auth_users][:id]).
227
274
  join(Sequel[:super_auth_permissions], id: Sequel[:permission_edges][:permission_id]).
@@ -231,43 +278,55 @@ class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
231
278
  end
232
279
 
233
280
  def users_resources
281
+ cast_type = string_cast_type
234
282
  SuperAuth::User.
235
283
  join(Sequel[:super_auth_edges].as(:user_edges), user_id: :id).
236
284
  select(
237
285
  Sequel[:super_auth_users][:id].as(:user_id),
238
286
  Sequel[:super_auth_users][:name].as(:user_name),
239
287
  Sequel[:super_auth_users][:external_id].as(:user_external_id),
240
- Sequel[:super_auth_users][:created_at].cast(:text).as(:user_created_at),
241
- Sequel[:super_auth_users][:updated_at].cast(:text).as(:user_updated_at),
242
-
243
- Sequel.lit(%Q[0 as "group_id"]), # Sequel[:groups][:group_id],
244
- Sequel::NULL.as(:group_name), # Sequel[:groups][:group_name],
245
- Sequel::NULL.as(:group_path), # Sequel[:groups][:group_path],
246
- Sequel::NULL.as(:group_name_path), # Sequel[:groups][:group_name_path],
247
- Sequel.lit(%Q[0 as "group_parent_id"]), # Sequel[:groups][:group_id],
248
- Sequel.lit(%Q['1970-01-01 00:00:00.000000-00' as "group_created_at"]), # Sequel[:groups][:group_created_at],
249
- Sequel.lit(%Q['1970-01-01 00:00:00.000000-00' as "group_updated_at"]), # Sequel[:groups][:group_updated_at],
250
-
251
-
252
- Sequel.lit(%Q[0 as "role_id"]), # Sequel[:roles][:role_id],
253
- Sequel::NULL.as(:role_name), # Sequel[:roles][:role_name],
254
- Sequel::NULL.as(:role_path), # Sequel[:roles][:role_path],
255
- Sequel::NULL.as(:role_name_path), # Sequel[:roles][:role_name_path],
256
- Sequel.lit(%Q[0 as "role_parent_id"]), # Sequel[:roles][:role_parent_id],
257
- Sequel::NULL.as(:role_created_at), # Sequel[:roles][:role_created_at],
258
- Sequel::NULL.as(:role_updated_at), # Sequel[:roles][:role_updated_at],
259
-
260
- Sequel.lit(%Q[0 as "permission_id"]),
261
- Sequel::NULL.as(:permission_name),
262
- Sequel.lit(%Q['1970-01-01 00:00:00.000000-00' as "permission_created_at"]),
263
- Sequel.lit(%Q['1970-01-01 00:00:00.000000-00' as "permission_updated_at"]),
288
+ Sequel[:super_auth_users][:external_type].as(:user_external_type),
289
+ Sequel[:super_auth_users][:created_at].cast(cast_type).as(:user_created_at),
290
+ Sequel[:super_auth_users][:updated_at].cast(cast_type).as(:user_updated_at),
291
+
292
+ Sequel.cast(nil, integer_cast_type).as(:group_id),
293
+ Sequel.cast(nil, string_cast_type).as(:group_name),
294
+ Sequel.cast(nil, string_cast_type).as(:group_path),
295
+ Sequel.cast(nil, string_cast_type).as(:group_name_path),
296
+ Sequel.cast(nil, integer_cast_type).as(:group_parent_id),
297
+ Sequel.cast(nil, string_cast_type).as(:group_created_at),
298
+ Sequel.cast(nil, string_cast_type).as(:group_updated_at),
299
+
300
+ Sequel.cast(nil, integer_cast_type).as(:role_id),
301
+ Sequel.cast(nil, string_cast_type).as(:role_name),
302
+ Sequel.cast(nil, string_cast_type).as(:role_path),
303
+ Sequel.cast(nil, string_cast_type).as(:role_name_path),
304
+ Sequel.cast(nil, integer_cast_type).as(:role_parent_id),
305
+ Sequel.cast(nil, string_cast_type).as(:role_created_at),
306
+ Sequel.cast(nil, string_cast_type).as(:role_updated_at),
307
+
308
+ Sequel.cast(nil, integer_cast_type).as(:permission_id),
309
+ Sequel.cast(nil, string_cast_type).as(:permission_name),
310
+ Sequel.cast(nil, string_cast_type).as(:permission_created_at),
311
+ Sequel.cast(nil, string_cast_type).as(:permission_updated_at),
264
312
 
265
313
  Sequel[:super_auth_resources][:id].as(:resource_id),
266
314
  Sequel[:super_auth_resources][:name].as(:resource_name),
267
- Sequel[:super_auth_resources][:external_id].as(:resource_external_id)
315
+ Sequel[:super_auth_resources][:external_id].as(:resource_external_id),
316
+ Sequel[:super_auth_resources][:external_type].as(:resource_external_type)
268
317
  ).
269
318
  join(Sequel[:super_auth_resources], Sequel[:user_edges][:resource_id] => Sequel[:super_auth_resources][:id]).
270
319
  distinct
271
320
  end
272
321
  end
322
+
323
+ def to_h
324
+ {
325
+ user: self&.user&.name,
326
+ group: self&.group&.name,
327
+ role: self&.role&.name,
328
+ resource: self&.resource&.name,
329
+ permission: self&.permission&.name,
330
+ }
331
+ end
273
332
  end
@@ -1,3 +1,4 @@
1
1
  class SuperAuth::Group < Sequel::Model(:super_auth_groups)
2
+ unrestrict_primary_key # For ActiveRecord
2
3
  include SuperAuth::Nestable
3
4
  end
@@ -25,6 +25,16 @@ module SuperAuth::Nestable
25
25
  end
26
26
 
27
27
  module ClassMethods
28
+ # Helper method to get the appropriate string cast type for the database
29
+ def string_cast_type
30
+ case SuperAuth.db.database_type
31
+ when :mysql, :mysql2
32
+ :char
33
+ else
34
+ :text
35
+ end
36
+ end
37
+
28
38
  def cte(id = nil, direction = :desc)
29
39
  model = self
30
40
  cte_name = model.cte_name
@@ -59,17 +69,14 @@ module SuperAuth::Nestable
59
69
  def with_descending_paths(base_ds, recursive_ds, cte_name)
60
70
  [
61
71
  base_ds.select_append(
62
- Sequel.function(
63
- :cast,
64
- Sequel[table_name][:id].as(:text)
65
- ).as(base_path)
72
+ Sequel[table_name][:id].cast(string_cast_type).as(base_path)
66
73
  ).select_append(Sequel[table_name][:name].as(base_name_path)),
67
74
 
68
75
  recursive_ds.select_append(
69
76
  Sequel.function(:concat,
70
- Sequel.function(:cast, Sequel[cte_name][base_path].as(:text)),
77
+ Sequel[cte_name][base_path].cast(string_cast_type),
71
78
  Sequel.lit("','"),
72
- Sequel.function(:cast, Sequel[pluralize][:id].as(:text)),
79
+ Sequel[pluralize][:id].cast(string_cast_type),
73
80
  ).as(base_path)
74
81
  ).select_append(
75
82
  Sequel.function(:concat,
@@ -83,12 +90,12 @@ module SuperAuth::Nestable
83
90
 
84
91
  def with_ascending_paths(base_ds, recursive_ds, cte_name)
85
92
  [
86
- base_ds.select_append(Sequel.function(:cast, Sequel[table_name][:id].as(:text)).as(base_path)).select_append(Sequel[table_name][:name].as(:base_name_path)),
93
+ base_ds.select_append(Sequel[table_name][:id].cast(string_cast_type).as(base_path)).select_append(Sequel[table_name][:name].as(base_name_path)),
87
94
  recursive_ds.select_append(
88
95
  Sequel.function(:concat,
89
- Sequel.function(:cast, Sequel[table_name][:id].as(:text)),
96
+ Sequel[table_name][:id].cast(string_cast_type),
90
97
  Sequel.lit("','"),
91
- Sequel.function(:cast, Sequel[cte_name][base_path].as(:text)),
98
+ Sequel[cte_name][base_path].cast(string_cast_type),
92
99
  ).as(base_path)
93
100
  ).select_append(
94
101
  Sequel.function(:concat,
@@ -129,4 +136,4 @@ module SuperAuth::Nestable
129
136
  "#{singularize(base)}_name_path".to_sym
130
137
  end
131
138
  end
132
- end
139
+ end
@@ -7,7 +7,7 @@ class SuperAuth::Permission < Sequel::Model(:super_auth_permissions)
7
7
  end
8
8
 
9
9
  def with_roles
10
- with_edges.join(SuperAuth::Role.from(SuperAuth::Role.trees).as(:roles), id: :role_id).select(
10
+ with_edges.join(SuperAuth::Role.from(SuperAuth::Role.trees).as(:roles), id: :role_id).select(
11
11
  Sequel[:super_auth_permissions][:id].as(:id),
12
12
  Sequel[:super_auth_permissions][:id].as(:permission_id),
13
13
  Sequel[:roles][:id].as(:role_id),