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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +78 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +8 -1
- data/README.md +286 -22
- data/USAGE.md +41 -16
- data/config/routes.rb +9 -71
- data/db/migrate/10_add_super_auth_label_to_resources.rb +13 -0
- data/db/migrate/1_users.rb +1 -1
- data/db/migrate/5_resources.rb +1 -1
- data/db/migrate/7_authorization.rb +2 -2
- data/db/migrate/8_add_indexes_to_edges.rb +7 -0
- data/db/migrate_activerecord/20250101000001_create_super_auth_users.rb +1 -1
- data/db/migrate_activerecord/20250101000005_create_super_auth_resources.rb +1 -1
- data/db/migrate_activerecord/20250101000007_create_super_auth_authorizations.rb +2 -2
- 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/install/templates/super_auth.rb +16 -5
- data/lib/generators/super_auth/rls/rls_generator.rb +22 -0
- data/lib/generators/super_auth/rls/templates/migration.rb.erb +15 -0
- data/lib/super_auth/active_record/by_current_user.rb +25 -5
- 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/edge.rb +65 -100
- 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/nestable.rb +41 -3
- data/lib/super_auth/railtie.rb +0 -2
- data/lib/super_auth/rls.rb +256 -0
- data/lib/super_auth/user.rb +3 -1
- data/lib/super_auth/version.rb +1 -1
- data/lib/super_auth.rb +85 -0
- data/lib/tasks/super_auth_tasks.rake +28 -0
- metadata +15 -9
- 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/config/routes.rb
CHANGED
|
@@ -1,73 +1,11 @@
|
|
|
1
|
+
require "super_auth/editor"
|
|
2
|
+
|
|
3
|
+
# The engine serves the graph editor at its mount point. Mount it inside your
|
|
4
|
+
# own authentication: it has none of its own.
|
|
5
|
+
#
|
|
6
|
+
# authenticate :admin do
|
|
7
|
+
# mount SuperAuth::Engine => "/super_auth"
|
|
8
|
+
# end
|
|
1
9
|
SuperAuth::Engine.routes.draw do
|
|
2
|
-
|
|
3
|
-
get '/', to: 'graph#index', as: :root
|
|
4
|
-
get '/graph', to: 'graph#index'
|
|
5
|
-
|
|
6
|
-
# Graph data API
|
|
7
|
-
get '/graph/data', to: 'graph#data'
|
|
8
|
-
get '/graph/orphaned', to: 'graph#orphaned'
|
|
9
|
-
post '/graph/compile_authorizations', to: 'graph#compile_authorizations'
|
|
10
|
-
|
|
11
|
-
# Authorization check
|
|
12
|
-
get '/graph/authorize', to: 'graph#authorize'
|
|
13
|
-
|
|
14
|
-
# Legacy visualization endpoint
|
|
15
|
-
get '/visualization', to: 'graph#visualization'
|
|
16
|
-
|
|
17
|
-
# CRUD operations for graph entities
|
|
18
|
-
scope :graph do
|
|
19
|
-
resources :users, only: [:create, :destroy], controller: 'graph' do
|
|
20
|
-
collection do
|
|
21
|
-
post '/', action: :create_user
|
|
22
|
-
end
|
|
23
|
-
member do
|
|
24
|
-
delete '/', action: :delete_user
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
resources :groups, only: [:create, :destroy], controller: 'graph' do
|
|
29
|
-
collection do
|
|
30
|
-
post '/', action: :create_group
|
|
31
|
-
end
|
|
32
|
-
member do
|
|
33
|
-
delete '/', action: :delete_group
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
resources :roles, only: [:create, :destroy], controller: 'graph' do
|
|
38
|
-
collection do
|
|
39
|
-
post '/', action: :create_role
|
|
40
|
-
end
|
|
41
|
-
member do
|
|
42
|
-
delete '/', action: :delete_role
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
resources :permissions, only: [:create, :destroy], controller: 'graph' do
|
|
47
|
-
collection do
|
|
48
|
-
post '/', action: :create_permission
|
|
49
|
-
end
|
|
50
|
-
member do
|
|
51
|
-
delete '/', action: :delete_permission
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
resources :graph_resources, only: [:create, :destroy], controller: 'graph', path: 'resources' do
|
|
56
|
-
collection do
|
|
57
|
-
post '/', action: :create_resource
|
|
58
|
-
end
|
|
59
|
-
member do
|
|
60
|
-
delete '/', action: :delete_resource
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
resources :edges, only: [:create, :destroy], controller: 'graph' do
|
|
65
|
-
collection do
|
|
66
|
-
post '/', action: :create_edge
|
|
67
|
-
end
|
|
68
|
-
member do
|
|
69
|
-
delete '/', action: :delete_edge
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
10
|
+
mount SuperAuth::Editor, at: "/"
|
|
73
11
|
end
|
data/db/migrate/1_users.rb
CHANGED
|
@@ -2,7 +2,7 @@ Sequel.migration do
|
|
|
2
2
|
up do
|
|
3
3
|
create_table(:super_auth_users) do
|
|
4
4
|
primary_key :id
|
|
5
|
-
|
|
5
|
+
column :external_id, SuperAuth.sequel_external_id_type # , null: false
|
|
6
6
|
String :external_type # , null: false
|
|
7
7
|
String :name
|
|
8
8
|
DateTime :created_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
data/db/migrate/5_resources.rb
CHANGED
|
@@ -3,7 +3,7 @@ Sequel.migration do
|
|
|
3
3
|
create_table(:super_auth_resources) do
|
|
4
4
|
primary_key :id
|
|
5
5
|
String :name
|
|
6
|
-
|
|
6
|
+
column :external_id, SuperAuth.sequel_external_id_type # , null: false
|
|
7
7
|
String :external_type # , null: false
|
|
8
8
|
DateTime :created_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
|
9
9
|
DateTime :updated_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
|
@@ -3,7 +3,7 @@ Sequel.migration do
|
|
|
3
3
|
create_table(:super_auth_authorizations) do
|
|
4
4
|
Integer :user_id, null: true
|
|
5
5
|
String :user_name, null: true
|
|
6
|
-
|
|
6
|
+
column :user_external_id, SuperAuth.sequel_external_id_type, null: true
|
|
7
7
|
String :user_external_type, null: true
|
|
8
8
|
DateTime :user_created_at, null: true
|
|
9
9
|
DateTime :user_updated_at, null: true
|
|
@@ -28,7 +28,7 @@ Sequel.migration do
|
|
|
28
28
|
DateTime :permission_updated_at, null: true
|
|
29
29
|
Integer :resource_id, null: true
|
|
30
30
|
String :resource_name, null: true
|
|
31
|
-
|
|
31
|
+
column :resource_external_id, SuperAuth.sequel_external_id_type, null: true
|
|
32
32
|
String :resource_external_type, null: true
|
|
33
33
|
DateTime :created_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
|
34
34
|
DateTime :updated_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
Sequel.migration do
|
|
2
|
+
# InnoDB already creates an index for every foreign key column, and will not
|
|
3
|
+
# drop one a constraint depends on, so on MySQL these indexes are redundant
|
|
4
|
+
# and their removal would fail. Skip them there.
|
|
2
5
|
up do
|
|
6
|
+
next if [:mysql, :mysql2].include?(database_type)
|
|
7
|
+
|
|
3
8
|
add_index :super_auth_edges, :user_id
|
|
4
9
|
add_index :super_auth_edges, :group_id
|
|
5
10
|
add_index :super_auth_edges, :role_id
|
|
@@ -8,6 +13,8 @@ Sequel.migration do
|
|
|
8
13
|
end
|
|
9
14
|
|
|
10
15
|
down do
|
|
16
|
+
next if [:mysql, :mysql2].include?(database_type)
|
|
17
|
+
|
|
11
18
|
drop_index :super_auth_edges, :user_id
|
|
12
19
|
drop_index :super_auth_edges, :group_id
|
|
13
20
|
drop_index :super_auth_edges, :role_id
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
class CreateSuperAuthUsers < ActiveRecord::Migration[7.0]
|
|
2
2
|
def change
|
|
3
3
|
create_table :super_auth_users do |t|
|
|
4
|
-
t.
|
|
4
|
+
t.column :external_id, SuperAuth.external_id_type
|
|
5
5
|
t.string :external_type
|
|
6
6
|
t.string :name
|
|
7
7
|
t.timestamps default: -> { "CURRENT_TIMESTAMP" }
|
|
@@ -2,7 +2,7 @@ class CreateSuperAuthResources < ActiveRecord::Migration[7.0]
|
|
|
2
2
|
def change
|
|
3
3
|
create_table :super_auth_resources do |t|
|
|
4
4
|
t.string :name
|
|
5
|
-
t.
|
|
5
|
+
t.column :external_id, SuperAuth.external_id_type
|
|
6
6
|
t.string :external_type
|
|
7
7
|
t.timestamps default: -> { "CURRENT_TIMESTAMP" }
|
|
8
8
|
end
|
|
@@ -3,7 +3,7 @@ class CreateSuperAuthAuthorizations < ActiveRecord::Migration[7.0]
|
|
|
3
3
|
create_table :super_auth_authorizations do |t|
|
|
4
4
|
t.integer :user_id
|
|
5
5
|
t.string :user_name
|
|
6
|
-
t.
|
|
6
|
+
t.column :user_external_id, SuperAuth.external_id_type
|
|
7
7
|
t.string :user_external_type
|
|
8
8
|
t.datetime :user_created_at
|
|
9
9
|
t.datetime :user_updated_at
|
|
@@ -32,7 +32,7 @@ class CreateSuperAuthAuthorizations < ActiveRecord::Migration[7.0]
|
|
|
32
32
|
|
|
33
33
|
t.integer :resource_id
|
|
34
34
|
t.string :resource_name
|
|
35
|
-
t.
|
|
35
|
+
t.column :resource_external_id, SuperAuth.external_id_type
|
|
36
36
|
t.string :resource_external_type
|
|
37
37
|
|
|
38
38
|
t.timestamps default: -> { "CURRENT_TIMESTAMP" }
|
|
@@ -6,24 +6,29 @@ Next steps:
|
|
|
6
6
|
|
|
7
7
|
1. Run migrations to create the database tables:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
rails db:migrate
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
(the engine adds its ActiveRecord migrations to your app)
|
|
12
12
|
|
|
13
|
-
2. Mount the engine in config/routes.rb
|
|
13
|
+
2. Mount the engine in config/routes.rb, inside your own authentication.
|
|
14
|
+
It serves the graph editor, which has no authentication of its own:
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
authenticate :admin do
|
|
17
|
+
mount SuperAuth::Engine => '/super_auth'
|
|
18
|
+
end
|
|
16
19
|
|
|
17
|
-
3. (Optional) Load sample data to
|
|
20
|
+
3. (Optional) Load sample data to have a graph to look at:
|
|
18
21
|
|
|
19
22
|
rails runner "load File.join(SuperAuth::Engine.root, 'db/seeds/sample_data.rb')"
|
|
20
23
|
|
|
21
|
-
4. Start your Rails server and
|
|
24
|
+
4. Start your Rails server and open the editor:
|
|
22
25
|
|
|
23
|
-
http://localhost:3000/super_auth
|
|
26
|
+
http://localhost:3000/super_auth
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
Edits change the graph; runtime access comes from the compiled table,
|
|
29
|
+
so press Recompile (or run SuperAuth::ActiveRecord::Authorization.compile!)
|
|
30
|
+
after editing.
|
|
31
|
+
|
|
32
|
+
For more information, see README.md ("Graph editor") and USAGE.md.
|
|
28
33
|
|
|
29
34
|
===============================================================================
|
|
@@ -2,8 +2,19 @@
|
|
|
2
2
|
# The SuperAuth Railtie automatically connects to your database and loads all
|
|
3
3
|
# models on boot. Use this file for any additional configuration.
|
|
4
4
|
#
|
|
5
|
-
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
5
|
+
SuperAuth.setup do |config|
|
|
6
|
+
# Column type for external id columns, created when the migrations run.
|
|
7
|
+
# Match your application's primary key type (:bigint, :uuid, :string, ...)
|
|
8
|
+
# so comparisons against your tables' pks are natively typed. Rails
|
|
9
|
+
# defaults to bigint pks. Must be set before running super_auth migrations.
|
|
10
|
+
config.external_id_type = :bigint
|
|
11
|
+
|
|
12
|
+
# Raise an error when a query runs without a current user set.
|
|
13
|
+
# Default is :none (returns empty results silently).
|
|
14
|
+
# config.missing_user_behavior = :raise
|
|
15
|
+
|
|
16
|
+
# Postgres row-level security: enable it per table with
|
|
17
|
+
# rails generate super_auth:rls Model ...
|
|
18
|
+
# then wrap request work in SuperAuth.as(current_user) { ... } wherever
|
|
19
|
+
# database-level enforcement should apply.
|
|
20
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'rails/generators'
|
|
2
|
+
require 'rails/generators/active_record'
|
|
3
|
+
|
|
4
|
+
module SuperAuth
|
|
5
|
+
module Generators
|
|
6
|
+
class RlsGenerator < Rails::Generators::Base
|
|
7
|
+
include ActiveRecord::Generators::Migration
|
|
8
|
+
|
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
|
10
|
+
|
|
11
|
+
# The model class names typed on the command line, verbatim:
|
|
12
|
+
# `rails g super_auth:rls Document Invoice` => ["Document", "Invoice"]
|
|
13
|
+
argument :model_names, type: :array, banner: "Model Model ..."
|
|
14
|
+
|
|
15
|
+
desc "Creates a migration enabling Postgres row-level security for the given models"
|
|
16
|
+
|
|
17
|
+
def create_migration_file
|
|
18
|
+
migration_template 'migration.rb.erb', 'db/migrate/enable_super_auth_rls.rb'
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class EnableSuperAuthRls < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def up
|
|
3
|
+
<% model_names.each do |model| -%>
|
|
4
|
+
SuperAuth::RLS.enable(:<%= model.tableize.tr('/', '_') %>, resource_type: "<%= model.camelize %>")
|
|
5
|
+
<% end -%>
|
|
6
|
+
# Roles allowed to bypass the policies (migrations, seeds, admin jobs):
|
|
7
|
+
# SuperAuth::RLS.grant_system(:app_admin)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def down
|
|
11
|
+
<% model_names.each do |model| -%>
|
|
12
|
+
SuperAuth::RLS.disable(:<%= model.tableize.tr('/', '_') %>)
|
|
13
|
+
<% end -%>
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
module SuperAuth::ActiveRecord::ByCurrentUser
|
|
2
|
+
# Records are filtered to those the current user holds an authorization for,
|
|
3
|
+
# keyed by the querying class's name. Because a subclass is its own resource
|
|
4
|
+
# type, privileged methods can be placed on a subclass whose access must be
|
|
5
|
+
# approved explicitly — a grant on the base class does not flow down:
|
|
6
|
+
#
|
|
7
|
+
# class Resource < ApplicationRecord
|
|
8
|
+
# super_auth
|
|
9
|
+
#
|
|
10
|
+
# class ResourceRestartPermission < Resource
|
|
11
|
+
# def restart!
|
|
12
|
+
# # dangerous restart operation
|
|
13
|
+
# end
|
|
14
|
+
# end
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# Resource::ResourceRestartPermission shares the base class's table and rows,
|
|
18
|
+
# but loading it requires an authorization whose resource_external_type is
|
|
19
|
+
# "Resource::ResourceRestartPermission" (edges to a SuperAuth::Resource
|
|
20
|
+
# registered with that external_type). If you can't load the object, you
|
|
21
|
+
# can't call the method.
|
|
2
22
|
def self.included(base)
|
|
3
23
|
base.send(:default_scope, **{all_queries: true}) do
|
|
4
24
|
if SuperAuth.current_user.blank?
|
|
@@ -10,7 +30,7 @@ module SuperAuth::ActiveRecord::ByCurrentUser
|
|
|
10
30
|
self
|
|
11
31
|
else
|
|
12
32
|
user_where =
|
|
13
|
-
if SuperAuth.
|
|
33
|
+
if SuperAuth.internal_user?(SuperAuth.current_user)
|
|
14
34
|
{ user_id: SuperAuth.current_user.id }
|
|
15
35
|
else
|
|
16
36
|
{ user_external_id: SuperAuth.current_user.id, user_external_type: SuperAuth.current_user.class.name }
|
|
@@ -26,7 +46,10 @@ module SuperAuth::ActiveRecord::ByCurrentUser
|
|
|
26
46
|
if type_level.exists?
|
|
27
47
|
self
|
|
28
48
|
else
|
|
29
|
-
# Per-record authorization: filter to specific records the user can
|
|
49
|
+
# Per-record authorization: filter to specific records the user can
|
|
50
|
+
# access. No type handling here: the external id columns are created
|
|
51
|
+
# with the app's pk type (SuperAuth.external_id_type at install
|
|
52
|
+
# time), so the comparison is natively typed.
|
|
30
53
|
where(
|
|
31
54
|
id: SuperAuth::ActiveRecord::Authorization
|
|
32
55
|
.where(**user_where, resource_external_type: resource_type)
|
|
@@ -36,7 +59,4 @@ module SuperAuth::ActiveRecord::ByCurrentUser
|
|
|
36
59
|
end
|
|
37
60
|
end
|
|
38
61
|
end
|
|
39
|
-
|
|
40
|
-
module ClassMethods
|
|
41
|
-
end
|
|
42
62
|
end
|
|
@@ -1,4 +1,45 @@
|
|
|
1
1
|
class SuperAuth::ActiveRecord::Resource < ActiveRecord::Base
|
|
2
2
|
self.table_name = 'super_auth_resources'
|
|
3
3
|
belongs_to :external, polymorphic: true, optional: true
|
|
4
|
+
|
|
5
|
+
# `super_auth_label` is a stored snapshot of the application record's human
|
|
6
|
+
# name, so the editor can render "Gulf War presumptive" instead of
|
|
7
|
+
# Claim#3a00b6fa and `super_auth-editor` can do it against a bare
|
|
8
|
+
# SUPER_AUTH_DATABASE_URL with no application loaded. It carries the prefix
|
|
9
|
+
# for the same reason the opt-in method on the host's model does: `label` is
|
|
10
|
+
# a name applications want for themselves.
|
|
11
|
+
#
|
|
12
|
+
# Deriving it here means a host that already syncs its resources gets labels
|
|
13
|
+
# with no extra wiring; renames still need refresh_label!, since they do not
|
|
14
|
+
# write this row.
|
|
15
|
+
before_save :set_label, if: :external_id?
|
|
16
|
+
|
|
17
|
+
# Re-derive the label after the application record is renamed. Hosts call it
|
|
18
|
+
# from whatever already syncs the node; super_auth:labels:backfill calls it
|
|
19
|
+
# for every row.
|
|
20
|
+
def refresh_label!
|
|
21
|
+
derived = derived_label
|
|
22
|
+
update_column(:super_auth_label, derived) unless derived.nil?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def set_label
|
|
28
|
+
derived = derived_label
|
|
29
|
+
self.super_auth_label = derived unless derived.nil?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# ponytail: a nil derivation never overwrites a stored label, in either
|
|
33
|
+
# path, and never fails the save. Three things derive nil and none of them
|
|
34
|
+
# means "this record has no name": RLS makes the application record
|
|
35
|
+
# unreadable without an asserted identity, external_type is a plain string
|
|
36
|
+
# that can name a class this process has not loaded, and type-level rows
|
|
37
|
+
# (external_id IS NULL) have no record to name at all. Writing nil for any
|
|
38
|
+
# of them would turn "this label is stale" into data, which is the failure
|
|
39
|
+
# this column exists to avoid.
|
|
40
|
+
def derived_label
|
|
41
|
+
SuperAuth.label_for(external)
|
|
42
|
+
rescue NameError
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
4
45
|
end
|
|
@@ -5,7 +5,9 @@ class SuperAuth::ActiveRecord::User < ActiveRecord::Base
|
|
|
5
5
|
|
|
6
6
|
def model_name = ActiveModel::Name.new(:user)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
# A read: runtime roles only get SELECT on this table. `.system` creates
|
|
9
|
+
# the row when missing and belongs to migrations, seeds and consoles.
|
|
10
|
+
def system? = self.class.find_by(name: "system") == self
|
|
9
11
|
def self.system = find_or_create_by(name: "system")
|
|
10
12
|
|
|
11
13
|
has_many :edges, class_name: 'SuperAuth::ActiveRecord::Edge'
|
|
@@ -1,2 +1,14 @@
|
|
|
1
1
|
class SuperAuth::Authorization < Sequel::Model(:super_auth_authorizations)
|
|
2
|
+
# Clears and repopulates the compiled table from the current graph, inside
|
|
3
|
+
# one transaction, and returns the row count. Row by row, like
|
|
4
|
+
# SuperAuth::ActiveRecord::Authorization.compile!; a single INSERT ... SELECT
|
|
5
|
+
# is a separate change. Runtime enforcement (ByCurrentUser, the RLS policies)
|
|
6
|
+
# reads only this table, so every edit to the graph is inert until this runs.
|
|
7
|
+
def self.compile!
|
|
8
|
+
db.transaction do
|
|
9
|
+
dataset.delete
|
|
10
|
+
SuperAuth::Edge.authorizations.each { |row| dataset.insert(row) }
|
|
11
|
+
dataset.count
|
|
12
|
+
end
|
|
13
|
+
end
|
|
2
14
|
end
|