5b89918debee638e6ebbdf99922a45b7330eeb31
[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]
4   skip_before_filter :render_404_if_no_object, only:
5     [:activate, :event_stream, :current, :system]
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 create
93                 if params[:openid_prefix]               # check if default openid_prefix needs to be overridden
94                         openid_prefix = params[:openid_prefix]
95                 else 
96                         openid_prefix = 'https://www.google.com/accounts/o8/id'         # default openid prefix
97                 end
98                 login_perm_props = {identity_url_prefix: openid_prefix}
99
100                 # check if only to probe the given user parameter
101                 just_probe = (params[:just_probe] == 'true') ? true : false;
102
103                 @object = model_class.new resource_attrs
104
105                 # If user_param is passed, lookup for user. If exists, skip create and create any missing links. 
106                 if params[:user_param]
107                         begin 
108                                 @object_found = find_user_from_user_param params[:user_param]
109                   end
110
111                         if !@object_found
112                                 @object[:email] = params[:user_param]                           
113                         need_to_create = true
114                         else
115                                 @object = @object_found
116                         end
117                 else            # need to create user for the given :user data
118                         need_to_create = true
119                 end
120
121                 # if just probing, return any object found      
122                 if just_probe 
123                         @object[:email] = nil   
124                         show
125                   return
126                 end
127
128                 # create if need be, and then create or update the links as needed 
129                 if need_to_create
130                         if @object.save         # save succeeded
131                                 oid_login_perm = Link.where(tail_uuid: @object[:email],
132                                                         head_kind: 'arvados#user',
133                                                         link_class: 'permission',
134                                                         name: 'can_login')
135
136                                 if [] == oid_login_perm
137                                         # create openid login permission
138                 oid_login_perm = Link.create(link_class: 'permission',
139                                            name: 'can_login',
140                                          tail_kind: 'email',
141                                          tail_uuid: @object[:email],
142                                          head_kind: 'arvados#user',
143                                          head_uuid: @object[:uuid],
144                                          properties: login_perm_props
145                                         )
146                                         logger.info { "openid login permission: " + oid_login_perm[:uuid] }
147                                 end
148           else
149                 raise "Save failed"
150                 end
151                 end
152
153                 # create links
154                 create_user_repo_link params[:repo_name]
155                 create_vm_login_permission_link params[:vm_uuid], params[:repo_name]
156                 create_user_group_link 
157
158                 show
159   end
160
161         protected 
162
163         # find the user from the given user parameter
164         def find_user_from_user_param(user_param)
165                 found_object = User.find_by_uuid user_param
166
167                 if !found_object
168                         begin
169                                 if !user_param.match(/\w\@\w+\.\w+/)
170                                         logger.warn ("Given user param is not valid email format: #{user_param}")
171                                         raise ArgumentError.new "User param is not of valid email format. Stop"
172                                 else
173           found_objects = User.where('email=?', user_param)  
174        
175                                         if found_objects.size > 1
176                                                 logger.warn ("Found #{found_objects.size} users with email #{user_param}. Stop.")
177                                                 raise ArgumentError.new "Found #{found_objects.size} users with email #{user_param}. Stop."
178                                         elsif found_objects.size == 1
179                                                 found_object = found_objects.first
180                                         end
181         end
182                 end
183                 end
184
185                 return found_object
186         end
187         
188         # link the repo_name passed
189         def create_user_repo_link(repo_name)
190                 if not repo_name
191                         logger.warn ("Repository name not given for #{@object[:uuid]}. Skip creating the link")
192                         return
193                 end
194
195                 # Check for an existing repository with the same name we're about to use.
196                 repo = (repositories = Repository.where(name: repo_name)) != nil ? repositories.first : nil
197                 if repo
198                 logger.warn "Repository already exists with name #{repo_name}: #{repo[:uuid]}. Will link to user."
199
200                         # Look for existing repository access (perhaps using a different repository/user name).
201                         repo_perms = Link.where(tail_uuid: @object[:uuid],
202                                 head_kind: 'arvados#repository',
203                                 head_uuid: repo[:uuid],
204                                 link_class: 'permission',
205                                 name: 'can_write')
206                         if [] != repo_perms
207                         logger.warn "User already has repository access " + repo_perms.collect { |p| p[:uuid] }.inspect
208                                 return
209                         end
210                 end
211
212                 repo ||= Repository.create(name: repo_name)             # create repo, if does not already exist
213                 logger.info { "repo uuid: " + repo[:uuid] }
214
215                 repo_perm = Link.create(tail_kind: 'arvados#user',
216                             tail_uuid: @object[:uuid],
217                             head_kind: 'arvados#repository',
218                             head_uuid: repo[:uuid],
219                             link_class: 'permission',
220                             name: 'can_write')
221                 logger.info { "repo permission: " + repo_perm[:uuid] }
222         end
223
224         # create login permission for the given vm_uuid, if it does not already exist
225         def create_vm_login_permission_link(vm_uuid, repo_name)
226                 # Look up the given virtual machine just to make sure it really exists.
227                 begin
228                         vm = (vms = VirtualMachine.where(uuid: vm_uuid)) != nil ? vms.first : nil
229                 #vm = VirtualMachine.where(uuid: vm_uuid)
230
231                         if not vm
232                           logger.warn "Could not look up virtual machine with uuid #{vm_uuid.inspect}"
233                                 return
234                         end
235
236                         logger.info { "vm uuid: " + vm[:uuid] }
237
238                         login_perm = Link.where(tail_uuid: @object[:uuid],
239                               head_uuid: vm[:uuid],
240                                 head_kind: 'arvados#virtualMachine',
241                                 link_class: 'permission',
242                                 name: 'can_login')
243                         if [] == login_perm
244                                 login_perm = Link.create(tail_kind: 'arvados#user',
245                                        tail_uuid: @object[:uuid],
246                                        head_kind: 'arvados#virtualMachine',
247                                        head_uuid: vm[:uuid],
248                                        link_class: 'permission',
249                                        name: 'can_login',
250                                        properties: {username: repo_name})
251                                 logger.info { "login permission: " + login_perm[:uuid] }
252                         end
253                 end
254         end
255
256         # add the user to the 'All users' group
257         def create_user_group_link
258                 # Look up the "All users" group (we expect uuid *-*-fffffffffffffff).
259                 group = Group.where(name: 'All users').select do |g|
260                         g[:uuid].match /-f+$/
261                 end.first
262
263                 if not group
264                 logger.warn "Could not look up the 'All users' group with uuid '*-*-fffffffffffffff'. Skip."
265                         return
266                 else
267                         logger.info { "\"All users\" group uuid: " + group[:uuid] }
268
269                         group_perm = Link.where(tail_uuid: @object[:uuid],
270                                                                                                                         head_uuid: group[:uuid],
271                                 head_kind: 'arvados#group',
272                                 link_class: 'permission',
273                                 name: 'can_read')
274
275                         if [] == group_perm
276                                 group_perm = Link.create(tail_kind: 'arvados#user',
277                                      tail_uuid: @object[:uuid],
278                                      head_kind: 'arvados#group',
279                                      head_uuid: group[:uuid],
280                                      link_class: 'permission',
281                                      name: 'can_read')
282                                 logger.info { "group permission: " + group_perm[:uuid] }
283                         end
284                 end
285         end
286  
287 end