Merge branch 'master' of git.clinicalfuture.com:arvados
[arvados.git] / services / api / app / models / user.rb
1 class User < ArvadosModel
2   include AssignUuid
3   include KindAndEtag
4   include CommonApiTemplate
5   serialize :prefs, Hash
6   has_many :api_client_authorizations
7   before_update :prevent_privilege_escalation
8   before_update :prevent_inactive_admin
9   after_create AdminNotifier
10
11   has_many :authorized_keys, :foreign_key => :authorized_user_uuid, :primary_key => :uuid
12
13   api_accessible :user, extend: :common do |t|
14     t.add :email
15     t.add :full_name
16     t.add :first_name
17     t.add :last_name
18     t.add :identity_url
19     t.add :is_active
20     t.add :is_admin
21     t.add :prefs
22   end
23
24   ALL_PERMISSIONS = {read: true, write: true, manage: true}
25
26   def full_name
27     "#{first_name} #{last_name}"
28   end
29
30   def groups_i_can(verb)
31     self.group_permissions.select { |uuid, mask| mask[verb] }.keys
32   end
33
34   def can?(actions)
35     actions.each do |action, target|
36       target_uuid = target
37       if target.respond_to? :uuid
38         target_uuid = target.uuid
39       end
40       next if target_uuid == self.uuid
41       next if (group_permissions[target_uuid] and
42                group_permissions[target_uuid][action])
43       if target.respond_to? :owner_uuid
44         next if target.owner_uuid == self.uuid
45         next if (group_permissions[target.owner_uuid] and
46                  group_permissions[target.owner_uuid][action])
47       end
48       return false
49     end
50     true
51   end
52
53   def self.invalidate_permissions_cache
54     Rails.cache.delete_matched(/^groups_for_user_/)
55   end
56
57   protected
58
59   def permission_to_update
60     # users must be able to update themselves (even if they are
61     # inactive) in order to create sessions
62     self == current_user or super
63   end
64
65   def permission_to_create
66     current_user.andand.is_admin or
67       (self == current_user and
68        self.is_active == Rails.configuration.new_users_are_active)
69   end
70
71   def prevent_privilege_escalation
72     if current_user.andand.is_admin
73       return true
74     end
75     if self.is_active_changed?
76       if self.is_active != self.is_active_was
77         logger.warn "User #{current_user.uuid} tried to change is_active from #{self.is_admin_was} to #{self.is_admin} for #{self.uuid}"
78         self.is_active = self.is_active_was
79       end
80     end
81     if self.is_admin_changed?
82       if self.is_admin != self.is_admin_was
83         logger.warn "User #{current_user.uuid} tried to change is_admin from #{self.is_admin_was} to #{self.is_admin} for #{self.uuid}"
84         self.is_admin = self.is_admin_was
85       end
86     end
87     true
88   end
89
90   def prevent_inactive_admin
91     if self.is_admin and not self.is_active
92       # There is no known use case for the strange set of permissions
93       # that would result from this change. It's safest to assume it's
94       # a mistake and disallow it outright.
95       raise "Admin users cannot be inactive"
96     end
97     true
98   end
99
100   def group_permissions
101     Rails.cache.fetch "groups_for_user_#{self.uuid}" do
102       permissions_from = {}
103       todo = {self.uuid => true}
104       done = {}
105       while !todo.empty?
106         lookup_uuids = todo.keys
107         lookup_uuids.each do |uuid| done[uuid] = true end
108         todo = {}
109         Link.where('tail_uuid in (?) and link_class = ? and head_kind = ?',
110                    lookup_uuids,
111                    'permission',
112                    'arvados#group').each do |link|
113           unless done.has_key? link.head_uuid
114             todo[link.head_uuid] = true
115           end
116           link_permissions = {}
117           case link.name
118           when 'can_read'
119             link_permissions = {read:true}
120           when 'can_write'
121             link_permissions = {read:true,write:true}
122           when 'can_manage'
123             link_permissions = ALL_PERMISSIONS
124           end
125           permissions_from[link.tail_uuid] ||= {}
126           permissions_from[link.tail_uuid][link.head_uuid] ||= {}
127           link_permissions.each do |k,v|
128             permissions_from[link.tail_uuid][link.head_uuid][k] ||= v
129           end
130         end
131       end
132       search_permissions(self.uuid, permissions_from)
133     end
134   end
135
136   def search_permissions(start, graph, merged={}, upstream_mask=nil, upstream_path={})
137     nextpaths = graph[start]
138     return merged if !nextpaths
139     return merged if upstream_path.has_key? start
140     upstream_path[start] = true
141     upstream_mask ||= ALL_PERMISSIONS
142     nextpaths.each do |head, mask|
143       merged[head] ||= {}
144       mask.each do |k,v|
145         merged[head][k] ||= v if upstream_mask[k]
146       end
147       search_permissions(head, graph, merged, upstream_mask.select { |k,v| v && merged[head][k] }, upstream_path)
148     end
149     upstream_path.delete start
150     merged
151   end
152 end