3036: Removed special case find_object_by_uuid. Converted some queries that
[arvados.git] / services / api / app / controllers / arvados / v1 / collections_controller.rb
1 class Arvados::V1::CollectionsController < ApplicationController
2   def create
3     owner_uuid = resource_attrs.delete(:owner_uuid) || current_user.uuid
4     unless current_user.can? write: owner_uuid
5       logger.warn "User #{current_user.andand.uuid} tried to set collection owner_uuid to #{owner_uuid}"
6       raise ArvadosModel::PermissionDeniedError
7     end
8
9     # Check permissions on the collection manifest.
10     # If any signature cannot be verified, return 403 Permission denied.
11     api_token = current_api_client_authorization.andand.api_token
12     signing_opts = {
13       key: Rails.configuration.blob_signing_key,
14       api_token: api_token,
15       ttl: Rails.configuration.blob_signing_ttl,
16     }
17     resource_attrs[:manifest_text].lines.each do |entry|
18       entry.split[1..-1].each do |tok|
19         if /^[[:digit:]]+:[[:digit:]]+:/.match tok
20           # This is a filename token, not a blob locator. Note that we
21           # keep checking tokens after this, even though manifest
22           # format dictates that all subsequent tokens will also be
23           # filenames. Safety first!
24         elsif Blob.verify_signature tok, signing_opts
25           # OK.
26         elsif Locator.parse(tok).andand.signature
27           # Signature provided, but verify_signature did not like it.
28           logger.warn "Invalid signature on locator #{tok}"
29           raise ArvadosModel::PermissionDeniedError
30         elsif Rails.configuration.permit_create_collection_with_unsigned_manifest
31           # No signature provided, but we are running in insecure mode.
32           logger.debug "Missing signature on locator #{tok} ignored"
33         elsif Blob.new(tok).empty?
34           # No signature provided -- but no data to protect, either.
35         else
36           logger.warn "Missing signature on locator #{tok}"
37           raise ArvadosModel::PermissionDeniedError
38         end
39       end
40     end
41
42     # Remove any permission signatures from the manifest.
43     resource_attrs[:manifest_text]
44       .gsub!(/ [[:xdigit:]]{32}(\+[[:digit:]]+)?(\+\S+)/) { |word|
45       word.strip!
46       loc = Locator.parse(word)
47       if loc
48         " " + loc.without_signature.to_s
49       else
50         " " + word
51       end
52     }
53
54     # Save the collection with the stripped manifest.
55     @object = model_class.new resource_attrs
56     @object.save!
57     show
58   end
59
60   def show
61     if current_api_client_authorization
62       signing_opts = {
63         key: Rails.configuration.blob_signing_key,
64         api_token: current_api_client_authorization.api_token,
65         ttl: Rails.configuration.blob_signing_ttl,
66       }
67       @object[:manifest_text]
68         .gsub!(/ [[:xdigit:]]{32}(\+[[:digit:]]+)?(\+\S+)/) { |word|
69         word.strip!
70         loc = Locator.parse(word)
71         if loc
72           " " + Blob.sign_locator(word, signing_opts)
73         else
74           " " + word
75         end
76       }
77     end
78     render json: @object.as_api_response(:with_data)
79   end
80
81   def script_param_edges(visited, sp)
82     case sp
83     when Hash
84       sp.each do |k, v|
85         script_param_edges(visited, v)
86       end
87     when Array
88       sp.each do |v|
89         script_param_edges(visited, v)
90       end
91     when String
92       return if sp.empty?
93       m = stripped_portable_data_hash(sp)
94       if m
95         generate_provenance_edges(visited, m)
96       end
97     end
98   end
99
100   def generate_provenance_edges(visited, uuid)
101     m = stripped_portable_data_hash(uuid)
102     uuid = m if m
103
104     if not uuid or uuid.empty? or visited[uuid]
105       return ""
106     end
107
108     logger.debug "visiting #{uuid}"
109
110     if m
111       # uuid is a collection
112       Collection.readable_by(current_user).where(portable_data_hash: uuid).each do |c|
113         visited[uuid] = c.as_api_response
114         visited[uuid][:files] = []
115         c.files.each do |f|
116           visited[uuid][:files] << f
117         end
118       end
119
120       Job.readable_by(current_user).where(output: uuid).each do |job|
121         generate_provenance_edges(visited, job.uuid)
122       end
123
124       Job.readable_by(current_user).where(log: uuid).each do |job|
125         generate_provenance_edges(visited, job.uuid)
126       end
127
128     else
129       # uuid is something else
130       rsc = ArvadosModel::resource_class_for_uuid uuid
131       if rsc == Job
132         Job.readable_by(current_user).where(uuid: uuid).each do |job|
133           visited[uuid] = job.as_api_response
134           script_param_edges(visited, job.script_parameters)
135         end
136       elsif rsc != nil
137         rsc.where(uuid: uuid).each do |r|
138           visited[uuid] = r.as_api_response
139         end
140       end
141     end
142
143     Link.readable_by(current_user).
144       where(head_uuid: uuid, link_class: "provenance").
145       each do |link|
146       visited[link.uuid] = link.as_api_response
147       generate_provenance_edges(visited, link.tail_uuid)
148     end
149   end
150
151   def provenance
152     visited = {}
153     generate_provenance_edges(visited, @object[:uuid])
154     render json: visited
155   end
156
157   def generate_used_by_edges(visited, uuid)
158     m = stripped_portable_data_hash(uuid)
159     uuid = m if m
160
161     if not uuid or uuid.empty? or visited[uuid]
162       return ""
163     end
164
165     logger.debug "visiting #{uuid}"
166
167     if m
168       # uuid is a collection
169       Collection.readable_by(current_user).where(portable_data_hash: uuid).each do |c|
170         visited[uuid] = c.as_api_response
171         visited[uuid][:files] = []
172         c.files.each do |f|
173           visited[uuid][:files] << f
174         end
175       end
176
177       if uuid == "d41d8cd98f00b204e9800998ecf8427e+0"
178         # special case for empty collection
179         return
180       end
181
182       Job.readable_by(current_user).where(["jobs.script_parameters like ?", "%#{uuid}%"]).each do |job|
183         generate_used_by_edges(visited, job.uuid)
184       end
185
186     else
187       # uuid is something else
188       rsc = ArvadosModel::resource_class_for_uuid uuid
189       if rsc == Job
190         Job.readable_by(current_user).where(uuid: uuid).each do |job|
191           visited[uuid] = job.as_api_response
192           generate_used_by_edges(visited, job.output)
193         end
194       elsif rsc != nil
195         rsc.where(uuid: uuid).each do |r|
196           visited[uuid] = r.as_api_response
197         end
198       end
199     end
200
201     Link.readable_by(current_user).
202       where(tail_uuid: uuid, link_class: "provenance").
203       each do |link|
204       visited[link.uuid] = link.as_api_response
205       generate_used_by_edges(visited, link.head_uuid)
206     end
207   end
208
209   def used_by
210     visited = {}
211     generate_used_by_edges(visited, @object[:uuid])
212     render json: visited
213   end
214
215 end