18004: Fixes a couple of race condition bugs related to caching remote users.
[arvados.git] / services / api / app / controllers / arvados / v1 / repositories_controller.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 class Arvados::V1::RepositoriesController < ApplicationController
6   skip_before_action :find_object_by_uuid, :only => :get_all_permissions
7   skip_before_action :render_404_if_no_object, :only => :get_all_permissions
8   before_action :admin_required, :only => :get_all_permissions
9
10   def get_all_permissions
11     # user_aks is a map of {user_uuid => array of public keys}
12     user_aks = {}
13     # admins is an array of user_uuids
14     admins = []
15     User.
16       where('users.is_active = ? or users.uuid = ?', true, anonymous_user_uuid).
17       eager_load(:authorized_keys).find_each do |u|
18       user_aks[u.uuid] = u.authorized_keys.collect do |ak|
19         {
20           public_key: ak.public_key,
21           authorized_key_uuid: ak.uuid
22         }
23       end
24       admins << u.uuid if u.is_admin
25     end
26     all_group_permissions = User.all_group_permissions
27     @repo_info = {}
28     Repository.eager_load(:permissions).find_each do |repo|
29       @repo_info[repo.uuid] = {
30         uuid: repo.uuid,
31         name: repo.name,
32         push_url: repo.push_url,
33         fetch_url: repo.fetch_url,
34         user_permissions: {},
35       }
36       # evidence is an array of {name: 'can_xxx', user_uuid: 'x-y-z'},
37       # one entry for each piece of evidence we find in the permission
38       # database that establishes that a user can access this
39       # repository. Multiple entries can be added for a given user,
40       # possibly with different access levels; these will be compacted
41       # below.
42       evidence = []
43       repo.permissions.each do |perm|
44         if ArvadosModel::resource_class_for_uuid(perm.tail_uuid) == Group
45           # A group has permission. Each user who has access to this
46           # group also has access to the repository. Access level is
47           # min(group-to-repo permission, user-to-group permission).
48           user_aks.each do |user_uuid, _|
49             perm_mask = all_group_permissions[user_uuid].andand[perm.tail_uuid]
50             if not perm_mask
51               next
52             elsif perm_mask[:manage] and perm.name == 'can_manage'
53               evidence << {name: 'can_manage', user_uuid: user_uuid}
54             elsif perm_mask[:write] and ['can_manage', 'can_write'].index perm.name
55               evidence << {name: 'can_write', user_uuid: user_uuid}
56             elsif perm_mask[:read]
57               evidence << {name: 'can_read', user_uuid: user_uuid}
58             end
59           end
60         elsif user_aks.has_key?(perm.tail_uuid)
61           # A user has permission; the user exists; and either the
62           # user is active, or it's the special case of the anonymous
63           # user which is never "active" but is allowed to read
64           # content from public repositories.
65           evidence << {name: perm.name, user_uuid: perm.tail_uuid}
66         end
67       end
68       # Owner of the repository, and all admins, can do everything.
69       ([repo.owner_uuid] | admins).each do |user_uuid|
70         # Except: no permissions for inactive users, even if they own
71         # repositories.
72         next unless user_aks.has_key?(user_uuid)
73         evidence << {name: 'can_manage', user_uuid: user_uuid}
74       end
75       # Distill all the evidence about permissions on this repository
76       # into one hash per user, of the form {'can_xxx' => true, ...}.
77       # The hash is nil for a user who has no permissions at all on
78       # this particular repository.
79       evidence.each do |perm|
80         user_uuid = perm[:user_uuid]
81         user_perms = (@repo_info[repo.uuid][:user_permissions][user_uuid] ||= {})
82         user_perms[perm[:name]] = true
83       end
84     end
85     # Revisit each {'can_xxx' => true, ...} hash for some final
86     # cleanup to make life easier for the requestor.
87     #
88     # Add a 'gitolite_permissions' key alongside the 'can_xxx' keys,
89     # for the convenience of the gitolite config file generator.
90     #
91     # Add all lesser permissions when a greater permission is
92     # present. If the requestor only wants to know who can write, it
93     # only has to test for 'can_write' in the response.
94     @repo_info.values.each do |repo|
95       repo[:user_permissions].each do |user_uuid, user_perms|
96         if user_perms['can_manage']
97           user_perms['gitolite_permissions'] = 'RW+'
98           user_perms['can_write'] = true
99           user_perms['can_read'] = true
100         elsif user_perms['can_write']
101           user_perms['gitolite_permissions'] = 'RW+'
102           user_perms['can_read'] = true
103         elsif user_perms['can_read']
104           user_perms['gitolite_permissions'] = 'R'
105         end
106       end
107     end
108     # The response looks like
109     #   {"kind":"...",
110     #    "repositories":[r1,r2,r3,...],
111     #    "user_keys":usermap}
112     # where each of r1,r2,r3 looks like
113     #   {"uuid":"repo-uuid-1",
114     #    "name":"username/reponame",
115     #    "push_url":"...",
116     #    "user_permissions":{"user-uuid-a":{"can_read":true,"gitolite_permissions":"R"}}}
117     # and usermap looks like
118     #   {"user-uuid-a":[{"public_key":"ssh-rsa g...","authorized_key_uuid":"ak-uuid-g"},...],
119     #    "user-uuid-b":[{"public_key":"ssh-rsa h...","authorized_key_uuid":"ak-uuid-h"},...],...}
120     send_json(kind: 'arvados#RepositoryPermissionSnapshot',
121               repositories: @repo_info.values,
122               user_keys: user_aks)
123   end
124 end