super_auth 0.7.0 → 0.9.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 +100 -0
- data/Gemfile.lock +1 -1
- data/README.md +440 -42
- data/USAGE.md +127 -21
- data/db/migrate/11_add_parent_id_to_resources.rb +32 -0
- data/db/migrate/12_add_resource_indexes.rb +116 -0
- data/db/migrate/13_add_resource_tree_guard.rb +28 -0
- data/db/migrate_activerecord/20250101000001_create_super_auth_users.rb +7 -1
- data/db/migrate_activerecord/20250101000002_create_super_auth_groups.rb +7 -1
- data/db/migrate_activerecord/20250101000003_create_super_auth_permissions.rb +7 -1
- data/db/migrate_activerecord/20250101000004_create_super_auth_roles.rb +7 -1
- data/db/migrate_activerecord/20250101000005_create_super_auth_resources.rb +7 -1
- data/db/migrate_activerecord/20250101000006_create_super_auth_edges.rb +7 -1
- data/db/migrate_activerecord/20250101000007_create_super_auth_authorizations.rb +7 -1
- data/db/migrate_activerecord/20250101000011_add_parent_id_to_super_auth_resources.rb +9 -0
- data/db/migrate_activerecord/20250101000012_add_super_auth_resource_indexes.rb +89 -0
- data/db/migrate_activerecord/20250101000013_add_resource_tree_guard_to_super_auth_resources.rb +15 -0
- data/db/seeds/sample_data.rb +1 -0
- data/lib/generators/super_auth/install/templates/README +6 -2
- data/lib/generators/super_auth/install/templates/super_auth.rb +6 -3
- data/lib/generators/super_auth/rls/templates/migration.rb.erb +2 -0
- data/lib/super_auth/active_record/authorization.rb +14 -3
- data/lib/super_auth/active_record/by_current_user.rb +136 -29
- data/lib/super_auth/active_record/group.rb +3 -0
- data/lib/super_auth/active_record/nested.rb +43 -0
- data/lib/super_auth/active_record/resource.rb +28 -4
- data/lib/super_auth/active_record/role.rb +3 -0
- data/lib/super_auth/active_record.rb +30 -2
- data/lib/super_auth/authorization.rb +63 -10
- data/lib/super_auth/edge.rb +73 -18
- data/lib/super_auth/editor/index.html +16 -10
- data/lib/super_auth/editor/seed.rb +11 -5
- data/lib/super_auth/editor.rb +35 -11
- data/lib/super_auth/nestable.rb +105 -4
- data/lib/super_auth/reach.rb +88 -0
- data/lib/super_auth/resource.rb +71 -0
- data/lib/super_auth/rls.rb +576 -44
- data/lib/super_auth/tree_guard.rb +115 -0
- data/lib/super_auth/version.rb +1 -1
- data/lib/super_auth.rb +55 -2
- metadata +10 -1
data/lib/super_auth/resource.rb
CHANGED
|
@@ -1,2 +1,73 @@
|
|
|
1
1
|
class SuperAuth::Resource < Sequel::Model(:super_auth_resources)
|
|
2
|
+
# Resources nest like groups and roles: a grant on a node reaches the node
|
|
3
|
+
# and every node under it (SuperAuth::Edge.join_resource_subtree). No
|
|
4
|
+
# unrestrict_primary_key, unlike those two: it exists for the ActiveRecord
|
|
5
|
+
# twins' descendants_dataset, which builds a Sequel model with an id, and
|
|
6
|
+
# the ActiveRecord Resource deliberately has none — nothing at runtime
|
|
7
|
+
# walks a resource tree.
|
|
8
|
+
include SuperAuth::Nestable
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
# A node with an external_type and no external_id is a type-level
|
|
12
|
+
# (wildcard) node: at runtime it means every record of that type, present
|
|
13
|
+
# and future (the ByCurrentUser type_level branch, the policy's
|
|
14
|
+
# `resource_external_id IS NULL OR` clause). A node with neither is a
|
|
15
|
+
# container. The type-level grant is a supported, permanent primitive —
|
|
16
|
+
# "this principal may act on every record of a type" has no cheaper
|
|
17
|
+
# spelling — and it is flat: what it may not do is join the tree.
|
|
18
|
+
def wildcards
|
|
19
|
+
exclude(external_type: nil).where(external_id: nil)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# The node registered for one record: the pair every host helper looks
|
|
23
|
+
# up before it grants, revokes or labels. It refuses a nil id rather than
|
|
24
|
+
# answering, because where(external_type: type, external_id: nil) is not
|
|
25
|
+
# "no node" — it IS the type-level node for that type, and a helper called
|
|
26
|
+
# with an unset foreign key would otherwise act on the grant that covers
|
|
27
|
+
# every record of the type.
|
|
28
|
+
def record(type, id)
|
|
29
|
+
if id.nil?
|
|
30
|
+
raise SuperAuth::Error, "SuperAuth::Resource.record(#{type.to_s.inspect}, nil): the id is nil. " \
|
|
31
|
+
"A node with an external_type and no external_id is the type-level node for every #{type} record, " \
|
|
32
|
+
"not the node for one of them; pass the record's id, or use wildcards for the type-level node."
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
first(external_type: type.to_s, external_id: id)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The recursive step of descendant_pairs stops at a type-level node. A
|
|
39
|
+
# per-record node nested under one would otherwise receive the
|
|
40
|
+
# type-level node's grants: one accidental parent_id, and a grant on
|
|
41
|
+
# "every Claim" also compiled a row for every claim node beneath it, on
|
|
42
|
+
# every path that reads the walk — including a host that reads
|
|
43
|
+
# Edge.authorizations directly and never calls compile!, where
|
|
44
|
+
# assert_compilable! does not run. The walk still anchors on the node a
|
|
45
|
+
# grant names, so a granted type-level node yields its own (type, NULL)
|
|
46
|
+
# row and nothing else; join_resource_subtree drops the other direction,
|
|
47
|
+
# a type-level node reached as a descendant.
|
|
48
|
+
def descend_from(parent)
|
|
49
|
+
Sequel.|({ Sequel[parent][:external_type] => nil }, Sequel.~(Sequel[parent][:external_id] => nil))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# "Type-level nodes are flat." compile! calls this before touching the
|
|
53
|
+
# compiled table, so a refused compile leaves the previous rows in place.
|
|
54
|
+
# The walk and the join above make the shape harmless; this makes it
|
|
55
|
+
# loud, because nobody means it. A type-level node with a parent looks
|
|
56
|
+
# like a container grant that reaches every record of a type, and one
|
|
57
|
+
# with children looks like a container whose children can only be
|
|
58
|
+
# reached through a grant that already covers them. One query: the
|
|
59
|
+
# type-level nodes that have a parent, or that some node names as its
|
|
60
|
+
# parent.
|
|
61
|
+
def assert_compilable!
|
|
62
|
+
parents = dataset.exclude(parent_id: nil).select(:parent_id)
|
|
63
|
+
nested = wildcards.where(Sequel.|(Sequel.~(parent_id: nil), { id: parents })).select_order_map(:id)
|
|
64
|
+
return if nested.empty?
|
|
65
|
+
|
|
66
|
+
raise SuperAuth::Error, "Wildcard resource nodes must be flat, but wildcard node(s) #{nested.join(', ')} " \
|
|
67
|
+
"have a parent or children. A resource node with an external_type and no external_id is a wildcard " \
|
|
68
|
+
"for every record of that type, not a container: nested in the tree it would compile to a row that " \
|
|
69
|
+
"reaches every record of its type through the tree. Move each to the root with no children, or give " \
|
|
70
|
+
"it an external_id."
|
|
71
|
+
end
|
|
72
|
+
end
|
|
2
73
|
end
|