Merge branch 'master' into 2187-enhance-user-setup
[arvados.git] / services / api / app / controllers / arvados / v1 / users_controller.rb
1 class Arvados::V1::UsersController < ApplicationController
2   skip_before_filter :find_object_by_uuid, only:
3     [:activate, :event_stream, :current, :system, :setup]
4   skip_before_filter :render_404_if_no_object, only:
5     [:activate, :event_stream, :current, :system, :setup]
6
7   def current
8     @object = current_user
9     show
10   end
11   def system
12     @object = system_user
13     show
14   end
15
16   class ChannelStreamer
17     Q_UPDATE_INTERVAL = 12
18     def initialize(opts={})
19       @opts = opts
20     end
21     def each
22       return unless @opts[:channel]
23       @redis = Redis.new(:timeout => 0)
24       @redis.subscribe(@opts[:channel]) do |event|
25         event.message do |channel, msg|
26           yield msg + "\n"
27         end
28       end
29     end
30   end
31       
32   def event_stream
33     channel = current_user.andand.uuid
34     if current_user.andand.is_admin
35       channel = params[:uuid] || channel
36     end
37     if client_accepts_plain_text_stream
38       self.response.headers['Last-Modified'] = Time.now.ctime.to_s
39       self.response_body = ChannelStreamer.new(channel: channel)
40     else
41       render json: {
42         href: url_for(uuid: channel),
43         comment: ('To retrieve the event stream as plain text, ' +
44                   'use a request header like "Accept: text/plain"')
45       }
46     end
47   end
48
49   def activate
50     if current_user.andand.is_admin && params[:uuid]
51       @object = User.find params[:uuid]
52     else
53       @object = current_user
54     end
55     if not @object.is_active
56       if not (current_user.is_admin or @object.is_invited)
57         logger.warn "User #{@object.uuid} called users.activate " +
58           "but is not invited"
59         raise ArgumentError.new "Cannot activate without being invited."
60       end
61       act_as_system_user do
62         required_uuids = Link.where(owner_uuid: system_user_uuid,
63                                     link_class: 'signature',
64                                     name: 'require',
65                                     tail_uuid: system_user_uuid,
66                                     head_kind: 'arvados#collection').
67           collect(&:head_uuid)
68         signed_uuids = Link.where(owner_uuid: system_user_uuid,
69                                   link_class: 'signature',
70                                   name: 'click',
71                                   tail_kind: 'arvados#user',
72                                   tail_uuid: @object.uuid,
73                                   head_kind: 'arvados#collection',
74                                   head_uuid: required_uuids).
75           collect(&:head_uuid)
76         todo_uuids = required_uuids - signed_uuids
77         if todo_uuids == []
78           @object.update_attributes is_active: true
79           logger.info "User #{@object.uuid} activated"
80         else
81           logger.warn "User #{@object.uuid} called users.activate " +
82             "before signing agreements #{todo_uuids.inspect}"
83           raise ArvadosModel::PermissionDeniedError.new \
84           "Cannot activate without user agreements #{todo_uuids.inspect}."
85         end
86       end
87     end
88     show
89   end
90
91   # create user object and all the needed links
92   def setup
93     # check if default openid_prefix needs to be overridden
94     if params[:openid_prefix]
95       openid_prefix = params[:openid_prefix]
96     else 
97       openid_prefix = Rails.configuration.openid_prefix
98     end
99     login_perm_props = {identity_url_prefix: openid_prefix}
100
101     @object = model_class.new resource_attrs
102
103     # Lookup for user. If exists, only create any missing links
104     @object_found = find_user_from_input 
105
106     if !@object_found
107       if !@object[:email]
108         raise "No email found in the input. Aborting user creation."
109       end
110
111       if @object.save
112         oid_login_perm = Link.where(tail_uuid: @object[:email],
113                                     head_kind: 'arvados#user',
114                                     link_class: 'permission',
115                                     name: 'can_login')
116
117         if [] == oid_login_perm
118           # create openid login permission
119           oid_login_perm = Link.create(link_class: 'permission',
120                                        name: 'can_login',
121                                        tail_kind: 'email',
122                                        tail_uuid: @object[:email],
123                                        head_kind: 'arvados#user',
124                                        head_uuid: @object[:uuid],
125                                        properties: login_perm_props
126                                       )
127           logger.info { "openid login permission: " + oid_login_perm[:uuid] }
128         end
129       else
130         raise "Save failed"
131       end
132     else
133       @object = @object_found
134     end
135     
136     # create links
137     create_user_repo_link params[:repo_name]
138     create_vm_login_permission_link params[:vm_uuid], params[:repo_name]
139     create_user_group_link 
140
141     show  
142   end
143
144   protected 
145
146   # find the user from the given user parameters
147   def find_user_from_input
148     if @object[:uuid]
149       found_object = User.find_by_uuid @object[:uuid]
150     end
151
152     if !found_object
153       if !@object[:email]
154         return
155       end
156
157       found_objects = User.where('email=?', @object[:email])  
158       found_object = found_objects.first
159     end
160
161     return found_object
162   end
163   
164   # link the repo_name passed
165   def create_user_repo_link(repo_name)
166     if not repo_name
167       logger.warn ("Repository name not given for #{@object[:uuid]}.")
168       return
169     end
170
171     # Check for an existing repository with the same name we're about to use.
172     repo = (repos = Repository.where(name: repo_name)) != nil ? repos.first : nil
173     if repo
174       logger.warn "Repository exists for #{repo_name}: #{repo[:uuid]}."
175
176       # Look for existing repository access for this repo
177       repo_perms = Link.where(tail_uuid: @object[:uuid],
178                               head_kind: 'arvados#repository',
179                               head_uuid: repo[:uuid],
180                               link_class: 'permission',
181                               name: 'can_write')
182       if [] != repo_perms
183         logger.warn "User already has repository access " + 
184             repo_perms.collect { |p| p[:uuid] }.inspect
185         return
186       end
187     end
188
189     # create repo, if does not already exist
190     repo ||= Repository.create(name: repo_name)
191     logger.info { "repo uuid: " + repo[:uuid] }
192
193     repo_perm = Link.create(tail_kind: 'arvados#user',
194                             tail_uuid: @object[:uuid],
195                             head_kind: 'arvados#repository',
196                             head_uuid: repo[:uuid],
197                             link_class: 'permission',
198                             name: 'can_write')
199     logger.info { "repo permission: " + repo_perm[:uuid] }
200   end
201
202   # create login permission for the given vm_uuid, if it does not already exist
203   def create_vm_login_permission_link(vm_uuid, repo_name)
204     # Look up the given virtual machine just to make sure it really exists.
205     begin
206       vm = (vms = VirtualMachine.where(uuid: vm_uuid)) != nil ? vms.first : nil
207       if not vm
208         logger.warn "Could not find virtual machine for #{vm_uuid.inspect}"
209         return
210       end
211
212       logger.info { "vm uuid: " + vm[:uuid] }
213
214       login_perm = Link.where(tail_uuid: @object[:uuid],
215                               head_uuid: vm[:uuid],
216                               head_kind: 'arvados#virtualMachine',
217                               link_class: 'permission',
218                               name: 'can_login')
219       if [] == login_perm
220         login_perm = Link.create(tail_kind: 'arvados#user',
221                                  tail_uuid: @object[:uuid],
222                                  head_kind: 'arvados#virtualMachine',
223                                  head_uuid: vm[:uuid],
224                                  link_class: 'permission',
225                                  name: 'can_login',
226                                  properties: {username: repo_name})
227         logger.info { "login permission: " + login_perm[:uuid] }
228       end
229     end
230   end
231
232   # add the user to the 'All users' group
233   def create_user_group_link
234     # Look up the "All users" group (we expect uuid *-*-fffffffffffffff).
235     group = Group.where(name: 'All users').select do |g|
236       g[:uuid].match /-f+$/
237     end.first
238
239     if not group
240       logger.warn "No 'All users' group with uuid '*-*-fffffffffffffff'."
241       return
242     else
243       logger.info { "\"All users\" group uuid: " + group[:uuid] }
244
245       group_perm = Link.where(tail_uuid: @object[:uuid],
246                               head_uuid: group[:uuid],
247                               head_kind: 'arvados#group',
248                               link_class: 'permission',
249                               name: 'can_read')
250
251       if [] == group_perm
252         group_perm = Link.create(tail_kind: 'arvados#user',
253                                  tail_uuid: @object[:uuid],
254                                  head_kind: 'arvados#group',
255                                  head_uuid: group[:uuid],
256                                  link_class: 'permission',
257                                  name: 'can_read')
258         logger.info { "group permission: " + group_perm[:uuid] }
259       end
260     end
261   end
262
263 end