Merge branch 'master' into 2272-setup-user-in-workbench
[arvados.git] / services / api / app / controllers / arvados / v1 / collections_controller.rb
1 class Arvados::V1::CollectionsController < ApplicationController
2   def create
3     # Collections are owned by system_user. Creating a collection has
4     # two effects: The collection is added if it doesn't already
5     # exist, and a "permission" Link is added (if one doesn't already
6     # exist) giving the current user (or specified owner_uuid)
7     # permission to read it.
8     owner_uuid = resource_attrs.delete(:owner_uuid) || current_user.uuid
9     owner_kind = if owner_uuid.match(/-(\w+)-/)[1] == User.uuid_prefix
10                    'arvados#user'
11                  else
12                    'arvados#group'
13                  end
14     unless current_user.can? write: owner_uuid
15       logger.warn "User #{current_user.andand.uuid} tried to set collection owner_uuid to #{owner_uuid}"
16       raise ArvadosModel::PermissionDeniedError
17     end
18     act_as_system_user do
19       @object = model_class.new resource_attrs.reject { |k,v| k == :owner_uuid }
20       begin
21         @object.save!
22       rescue ActiveRecord::RecordNotUnique
23         logger.debug resource_attrs.inspect
24         if resource_attrs[:manifest_text] and resource_attrs[:uuid]
25           @existing_object = model_class.
26             where('uuid=? and manifest_text=?',
27                   resource_attrs[:uuid],
28                   resource_attrs[:manifest_text]).
29             first
30           @object = @existing_object || @object
31         end
32       end
33
34       if @object
35         link_attrs = {
36           owner_uuid: owner_uuid,
37           link_class: 'permission',
38           name: 'can_read',
39           head_kind: 'arvados#collection',
40           head_uuid: @object.uuid,
41           tail_kind: owner_kind,
42           tail_uuid: owner_uuid
43         }
44         ActiveRecord::Base.transaction do
45           if Link.where(link_attrs).empty?
46             Link.create! link_attrs
47           end
48         end
49       end
50     end
51     show
52   end
53
54   def show
55     render json: @object.as_api_response(:with_data)
56   end
57
58   def collection_uuid(uuid)
59     m = /([a-f0-9]{32}(\+[0-9]+)?)(\+.*)?/.match(uuid)
60     if m
61       m[1]
62     else
63       nil
64     end
65   end
66
67   def script_param_edges(visited, sp)
68     case sp
69     when Hash
70       sp.each do |k, v|
71         script_param_edges(visited, v)
72       end
73     when Array
74       sp.each do |v|
75         script_param_edges(visited, v)
76       end
77     when String
78       return if sp.empty?
79       m = collection_uuid(sp)
80       if m
81         generate_provenance_edges(visited, m)
82       end
83     end
84   end
85
86   def generate_provenance_edges(visited, uuid)
87     m = collection_uuid(uuid)
88     uuid = m if m
89
90     if not uuid or uuid.empty? or visited[uuid]
91       return ""
92     end
93
94     logger.debug "visiting #{uuid}"
95
96     if m  
97       # uuid is a collection
98       Collection.readable_by(current_user).where(uuid: uuid).each do |c|
99         visited[uuid] = c.as_api_response
100         visited[uuid][:files] = []
101         c.files.each do |f|
102           visited[uuid][:files] << f
103         end
104       end
105
106       Job.readable_by(current_user).where(output: uuid).each do |job|
107         generate_provenance_edges(visited, job.uuid)
108       end
109
110       Job.readable_by(current_user).where(log: uuid).each do |job|
111         generate_provenance_edges(visited, job.uuid)
112       end
113       
114     else
115       # uuid is something else
116       rsc = ArvadosModel::resource_class_for_uuid uuid
117       if rsc == Job
118         Job.readable_by(current_user).where(uuid: uuid).each do |job|
119           visited[uuid] = job.as_api_response
120           script_param_edges(visited, job.script_parameters)
121         end
122       elsif rsc != nil
123         rsc.where(uuid: uuid).each do |r|
124           visited[uuid] = r.as_api_response
125         end
126       end
127     end
128
129     Link.readable_by(current_user).
130       where(head_uuid: uuid, link_class: "provenance").
131       each do |link|
132       visited[link.uuid] = link.as_api_response
133       generate_provenance_edges(visited, link.tail_uuid)
134     end
135
136     #puts "finished #{uuid}"
137   end
138
139   def provenance
140     visited = {}
141     generate_provenance_edges(visited, @object[:uuid])
142     render json: visited
143   end
144
145   def generate_used_by_edges(visited, uuid)
146     m = collection_uuid(uuid)
147     uuid = m if m
148
149     if not uuid or uuid.empty? or visited[uuid]
150       return ""
151     end
152
153     logger.debug "visiting #{uuid}"
154
155     if m  
156       # uuid is a collection
157       Collection.readable_by(current_user).where(uuid: uuid).each do |c|
158         visited[uuid] = c.as_api_response
159         visited[uuid][:files] = []
160         c.files.each do |f|
161           visited[uuid][:files] << f
162         end
163       end
164
165       if uuid == "d41d8cd98f00b204e9800998ecf8427e+0"
166         # special case for empty collection
167         return
168       end
169
170       Job.readable_by(current_user).where(["jobs.script_parameters like ?", "%#{uuid}%"]).each do |job|
171         generate_used_by_edges(visited, job.uuid)
172       end
173       
174     else
175       # uuid is something else
176       rsc = ArvadosModel::resource_class_for_uuid uuid
177       if rsc == Job
178         Job.readable_by(current_user).where(uuid: uuid).each do |job|
179           visited[uuid] = job.as_api_response
180           generate_used_by_edges(visited, job.output)
181         end
182       elsif rsc != nil
183         rsc.where(uuid: uuid).each do |r|
184           visited[uuid] = r.as_api_response
185         end
186       end
187     end
188
189     Link.readable_by(current_user).
190       where(tail_uuid: uuid, link_class: "provenance").
191       each do |link|
192       visited[link.uuid] = link.as_api_response
193       generate_used_by_edges(visited, link.head_uuid)
194     end
195
196     #puts "finished #{uuid}"
197   end
198
199   def used_by
200     visited = {}
201     generate_used_by_edges(visited, @object[:uuid])
202     render json: visited
203   end
204
205   protected
206   def find_object_by_uuid
207     super
208     if !@object and !params[:uuid].match(/^[0-9a-f]+\+\d+$/)
209       # Normalize the given uuid and search again.
210       hash_part = params[:uuid].match(/^([0-9a-f]*)/)[1]
211       collection = Collection.where('uuid like ?', hash_part + '+%').first
212       if collection
213         # We know the collection exists, and what its real uuid is in
214         # the database. Now, throw out @objects and repeat the usual
215         # lookup procedure. (Returning the collection at this point
216         # would bypass permission checks.)
217         @objects = nil
218         @where = { uuid: collection.uuid }
219         find_objects_for_index
220         @object = @objects.first
221       end
222     end
223   end
224
225 end