Merge branch '18273-java-timeout' refs #18273
[arvados.git] / services / api / db / migrate / 20200501150153_permission_table.rb
index 7b9a9981390160c9466f2f37d7a045bc39a3b93b..4f9ea156dc8d8315004aed0e761c69ba8e431de6 100644 (file)
@@ -18,7 +18,6 @@ class PermissionTable < ActiveRecord::Migration[5.0]
     # permissions system.  Updating trashed items follows a similar
     # (but less complicated) strategy to updating permissions, so it
     # may be helpful to look at that first.
-    #
 
     ActiveRecord::Base.connection.execute "DROP MATERIALIZED VIEW IF EXISTS materialized_permission_view;"
     drop_table :permission_refresh_lock
@@ -99,20 +98,24 @@ $$;
 }
 
     # Merge all permission relationships into a single view.  This
-    # consists of: groups (projects) owning things, users owning
-    # things, users owning themselves, and explicit permission links.
+    # consists of: groups owned by users and projects, users owned
+    # by other users, users have permission on themselves,
+    # and explicit permission links.
     #
     # A SQL view gets inlined into the query where it is used as a
     # subquery.  This enables the query planner to inject constraints,
-    # so we only look up edges we plan to traverse and avoid a brute
+    # so it only has to look up edges it plans to traverse and avoid a brute
     # force query of all edges.
     ActiveRecord::Base.connection.execute %{
 create view permission_graph_edges as
-  select groups.owner_uuid as tail_uuid, groups.uuid as head_uuid, (3) as val from groups
+  select groups.owner_uuid as tail_uuid, groups.uuid as head_uuid,
+         (3) as val, groups.uuid as edge_id from groups
 union all
-  select users.owner_uuid as tail_uuid, users.uuid as head_uuid, (3) as val from users
+  select users.owner_uuid as tail_uuid, users.uuid as head_uuid,
+         (3) as val, users.uuid as edge_id from users
 union all
-  select users.uuid as tail_uuid, users.uuid as head_uuid, (3) as val from users
+  select users.uuid as tail_uuid, users.uuid as head_uuid,
+         (3) as val, '' as edge_id from users
 union all
   select links.tail_uuid,
          links.head_uuid,
@@ -122,34 +125,35 @@ union all
            WHEN links.name = 'can_write'  THEN 2
            WHEN links.name = 'can_manage' THEN 3
            ELSE 0
-          END as val
+         END as val,
+         links.uuid as edge_id
       from links
       where links.link_class='permission'
 }
 
-    # Code fragment that is used below.  This is used to ensure that
-    # the permission edge passed into compute_permission_subgraph
-    # takes precedence over an existing edge in the "edges" view.
-    override = %{,
-                            case (edges.tail_uuid = perm_origin_uuid AND
-                                  edges.head_uuid = starting_uuid)
+    # This is used to ensure that the permission edge passed into
+    # compute_permission_subgraph takes replaces the existing edge in
+    # the "edges" view that is about to be removed.
+    edge_perm = %{
+case (edges.edge_id = perm_edge_id)
                                when true then starting_perm
-                               else null
+                               else edges.val
                             end
 }
 
-    #
     # The primary function to compute permissions for a subgraph.
-    # This originally was organized somewhat more cleanly, but this
-    # ran into performance issues due to the query optimizer not
-    # working across function and "with" expression boundaries.  So I
-    # had to fall back on using string templates for the repeated
-    # code.  I'm sorry.
+    # Comments on how it works are inline.
+    #
+    # Due to performance issues due to the query optimizer not
+    # working across function and "with" expression boundaries, I
+    # had to fall back on using string templates for repeated code
+    # in order to inline it.
 
     ActiveRecord::Base.connection.execute %{
 create or replace function compute_permission_subgraph (perm_origin_uuid varchar(27),
                                                         starting_uuid varchar(27),
-                                                        starting_perm integer)
+                                                        starting_perm integer,
+                                                        perm_edge_id varchar(27))
 returns table (user_uuid varchar(27), target_uuid varchar(27), val integer, traverse_owned bool)
 STABLE
 language SQL
@@ -168,6 +172,14 @@ as $$
                   starting_uuid One of 1, 2, 3 for can_read,
                   can_write, can_manage respectively, or 0 to revoke
                   permissions.
+
+   perm_edge_id: Identifies the permission edge that is being updated.
+                 Changes of ownership, this is starting_uuid.
+                 For links, this is the uuid of the link object.
+                 This is used to override the edge value in the database
+                 with starting_perm.  This is necessary when revoking
+                 permissions because the update happens before edge is
+                 actually removed.
 */
 with
   /* Starting from starting_uuid, determine the set of objects that
@@ -182,7 +194,7 @@ with
                     should_traverse_owned(starting_uuid, starting_perm),
                     (perm_origin_uuid = starting_uuid or starting_uuid not like '_____-tpzed-_______________'))
 },
-:override => override
+:edge_perm => edge_perm
 } }),
 
   /* Find other inbound edges that grant permissions to 'targets' in
@@ -207,12 +219,11 @@ with
            should_traverse_owned(edges.head_uuid, edges.val),
            edges.head_uuid like '_____-j7d0g-_______________'
       from permission_graph_edges as edges
-      where (not (edges.tail_uuid = perm_origin_uuid and
-                  edges.head_uuid = starting_uuid)) and
+      where edges.edge_id != perm_edge_id and
             edges.tail_uuid not in (select target_uuid from perm_from_start where target_uuid like '_____-j7d0g-_______________') and
             edges.head_uuid in (select target_uuid from perm_from_start)
 },
-:override => override
+:edge_perm => edge_perm
 } }),
 
   /* Combine the permissions computed in the first two phases. */
@@ -278,7 +289,7 @@ $$;
     drop_table :trashed_groups
 
     ActiveRecord::Base.connection.execute "DROP function project_subtree_with_trash_at (varchar, timestamp);"
-    ActiveRecord::Base.connection.execute "DROP function compute_permission_subgraph (varchar, varchar, integer);"
+    ActiveRecord::Base.connection.execute "DROP function compute_permission_subgraph (varchar, varchar, integer, varchar);"
     ActiveRecord::Base.connection.execute "DROP function should_traverse_owned(varchar, integer);"
     ActiveRecord::Base.connection.execute "DROP view permission_graph_edges;"