16007: Make sure trash table gets refreshed on database reset
[arvados.git] / services / api / lib / refresh_permission_view.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 PERMISSION_VIEW = "materialized_permissions"
6 TRASHED_GROUPS = "trashed_groups"
7
8 def refresh_permission_view
9   ActiveRecord::Base.transaction do
10     ActiveRecord::Base.connection.execute("LOCK TABLE #{PERMISSION_VIEW}")
11     ActiveRecord::Base.connection.execute("DELETE FROM #{PERMISSION_VIEW}")
12     ActiveRecord::Base.connection.execute %{
13 INSERT INTO #{PERMISSION_VIEW}
14 select users.uuid, g.target_uuid, g.val, g.traverse_owned
15 from users, lateral search_permission_graph(users.uuid, 3) as g where g.val > 0
16 },
17                                           "refresh_permission_view.do"
18   end
19 end
20
21 def refresh_trashed
22   ActiveRecord::Base.connection.execute("DELETE FROM #{TRASHED_GROUPS}")
23   ActiveRecord::Base.connection.execute("INSERT INTO #{TRASHED_GROUPS} select * from compute_trashed()")
24 end
25
26 def update_permissions perm_origin_uuid, starting_uuid, perm_level, check=true
27   # Update a subset of the permission graph
28   # perm_level is the inherited permission
29   # perm_level is a number from 0-3
30   #   can_read=1
31   #   can_write=2
32   #   can_manage=3
33   #   call with perm_level=0 to revoke permissions
34   #
35   # 1. Compute set (group, permission) implied by traversing
36   #    graph starting at this group
37   # 2. Find links from outside the graph that point inside
38   # 3. For each starting uuid, get the set of permissions from the
39   #    materialized permission table
40   # 3. Delete permissions from table not in our computed subset.
41   # 4. Upsert each permission in our subset (user, group, val)
42
43   ActiveRecord::Base.connection.execute "LOCK TABLE #{PERMISSION_VIEW} in SHARE MODE"
44
45   temptable_perms = "temp_perms_#{rand(2**64).to_s(10)}"
46   ActiveRecord::Base.connection.exec_query %{
47 create temporary table #{temptable_perms} on commit drop
48 as select * from compute_permission_subgraph($1, $2, $3)
49 },
50                                            'update_permissions.select',
51                                            [[nil, perm_origin_uuid],
52                                             [nil, starting_uuid],
53                                             [nil, perm_level]]
54
55   ActiveRecord::Base.connection.exec_delete %{
56 delete from #{PERMISSION_VIEW} where
57   target_uuid in (select target_uuid from #{temptable_perms}) and
58   not exists (select 1 from #{temptable_perms}
59               where target_uuid=#{PERMISSION_VIEW}.target_uuid and
60                     user_uuid=#{PERMISSION_VIEW}.user_uuid and
61                     val>0)
62 },
63                                         "update_permissions.delete"
64
65   ActiveRecord::Base.connection.exec_query %{
66 insert into #{PERMISSION_VIEW} (user_uuid, target_uuid, perm_level, traverse_owned)
67   select user_uuid, target_uuid, val as perm_level, traverse_owned from #{temptable_perms} where val>0
68 on conflict (user_uuid, target_uuid) do update set perm_level=EXCLUDED.perm_level, traverse_owned=EXCLUDED.traverse_owned;
69 },
70                                            "update_permissions.insert"
71
72   if check and perm_level>0
73     check_permissions_against_full_refresh
74   end
75 end
76
77
78 def check_permissions_against_full_refresh
79   #
80   # For debugging, this checks contents of the
81   # incrementally-updated 'materialized_permission' against a
82   # from-scratch permission refresh.
83   #
84
85   q1 = ActiveRecord::Base.connection.exec_query %{
86 select user_uuid, target_uuid, perm_level, traverse_owned from #{PERMISSION_VIEW}
87 order by user_uuid, target_uuid
88 }
89
90   q2 = ActiveRecord::Base.connection.exec_query %{
91 select users.uuid as user_uuid, g.target_uuid, g.val as perm_level, g.traverse_owned
92 from users, lateral search_permission_graph(users.uuid, 3) as g where g.val > 0
93 order by users.uuid, target_uuid
94 }
95
96   if q1.count != q2.count
97     puts "Didn't match incremental+: #{q1.count} != full refresh-: #{q2.count}"
98   end
99
100   if q1.count > q2.count
101     q1.each_with_index do |r, i|
102       if r != q2[i]
103         puts "+#{r}\n-#{q2[i]}"
104         raise "Didn't match"
105       end
106     end
107   else
108     q2.each_with_index do |r, i|
109       if r != q1[i]
110         puts "+#{q1[i]}\n-#{r}"
111         raise "Didn't match"
112       end
113     end
114   end
115 end