1 class Arvados::V1::CollectionsController < ApplicationController
3 if !resource_attrs[:manifest_text]
4 return send_error("'manifest_text' attribute must be specified",
5 status: :unprocessable_entity)
8 # Check permissions on the collection manifest.
9 # If any signature cannot be verified, return 403 Permission denied.
10 api_token = current_api_client_authorization.andand.api_token
12 key: Rails.configuration.blob_signing_key,
14 ttl: Rails.configuration.blob_signing_ttl,
16 resource_attrs[:manifest_text].lines.each do |entry|
17 entry.split[1..-1].each do |tok|
18 if /^[[:digit:]]+:[[:digit:]]+:/.match tok
19 # This is a filename token, not a blob locator. Note that we
20 # keep checking tokens after this, even though manifest
21 # format dictates that all subsequent tokens will also be
22 # filenames. Safety first!
23 elsif Blob.verify_signature tok, signing_opts
25 elsif Locator.parse(tok).andand.signature
26 # Signature provided, but verify_signature did not like it.
27 logger.warn "Invalid signature on locator #{tok}"
28 raise ArvadosModel::PermissionDeniedError
29 elsif Rails.configuration.permit_create_collection_with_unsigned_manifest
30 # No signature provided, but we are running in insecure mode.
31 logger.debug "Missing signature on locator #{tok} ignored"
32 elsif Blob.new(tok).empty?
33 # No signature provided -- but no data to protect, either.
35 logger.warn "Missing signature on locator #{tok}"
36 raise ArvadosModel::PermissionDeniedError
41 # Remove any permission signatures from the manifest.
42 munge_manifest_locators(resource_attrs[:manifest_text]) do |loc|
43 loc.without_signature.to_s
49 def find_object_by_uuid
50 if loc = Locator.parse(params[:id])
52 if c = Collection.readable_by(*@read_users).where({ portable_data_hash: loc.to_s }).limit(1).first
54 portable_data_hash: c.portable_data_hash,
55 manifest_text: c.manifest_text,
57 data_size: c.data_size
67 sign_manifests(@object[:manifest_text])
72 sign_manifests(*@objects.map { |c| c[:manifest_text] })
76 def script_param_edges(visited, sp)
80 script_param_edges(visited, v)
84 script_param_edges(visited, v)
88 if loc = Locator.parse(sp)
89 search_edges(visited, loc.to_s, :search_up)
94 def search_edges(visited, uuid, direction)
95 if uuid.nil? or uuid.empty? or visited[uuid]
99 if loc = Locator.parse(uuid)
101 return if visited[loc.to_s]
104 logger.debug "visiting #{uuid}"
107 # uuid is a portable_data_hash
108 if c = Collection.readable_by(*@read_users).where(portable_data_hash: loc.to_s).limit(1).first
109 visited[loc.to_s] = {
110 portable_data_hash: c.portable_data_hash,
112 data_size: c.data_size
116 if direction == :search_up
117 # Search upstream for jobs where this locator is the output of some job
118 Job.readable_by(*@read_users).where(output: loc.to_s).each do |job|
119 search_edges(visited, job.uuid, :search_up)
122 Job.readable_by(*@read_users).where(log: loc.to_s).each do |job|
123 search_edges(visited, job.uuid, :search_up)
125 elsif direction == :search_down
126 if loc.to_s == "d41d8cd98f00b204e9800998ecf8427e+0"
127 # Special case, don't follow the empty collection.
131 # Search downstream for jobs where this locator is in script_parameters
132 Job.readable_by(*@read_users).where(["jobs.script_parameters like ?", "%#{loc.to_s}%"]).each do |job|
133 search_edges(visited, job.uuid, :search_down)
137 # uuid is a regular Arvados UUID
138 rsc = ArvadosModel::resource_class_for_uuid uuid
140 Job.readable_by(*@read_users).where(uuid: uuid).each do |job|
141 visited[uuid] = job.as_api_response
142 if direction == :search_up
143 # Follow upstream collections referenced in the script parameters
144 script_param_edges(visited, job.script_parameters)
145 elsif direction == :search_down
146 # Follow downstream job output
147 search_edges(visited, job.output, direction)
150 elsif rsc == Collection
151 if c = Collection.readable_by(*@read_users).where(uuid: uuid).limit(1).first
152 search_edges(visited, c.portable_data_hash, direction)
153 visited[c.portable_data_hash] = c.as_api_response
156 rsc.where(uuid: uuid).each do |r|
157 visited[uuid] = r.as_api_response
162 if direction == :search_up
163 # Search for provenance links pointing to the current uuid
164 Link.readable_by(*@read_users).
165 where(head_uuid: uuid, link_class: "provenance").
167 visited[link.uuid] = link.as_api_response
168 search_edges(visited, link.tail_uuid, direction)
170 elsif direction == :search_down
171 # Search for provenance links emanating from the current uuid
172 Link.readable_by(current_user).
173 where(tail_uuid: uuid, link_class: "provenance").
175 visited[link.uuid] = link.as_api_response
176 search_edges(visited, link.head_uuid, direction)
183 search_edges(visited, @object[:uuid] || @object[:portable_data_hash], :search_up)
189 search_edges(visited, @object[:uuid] || @object[:portable_data_hash], :search_down)
193 def self.munge_manifest_locators(manifest)
194 # Given a manifest text and a block, yield each locator,
195 # and replace it with whatever the block returns.
196 manifest.andand.gsub!(/ [[:xdigit:]]{32}(\+[[:digit:]]+)?(\+\S+)/) do |word|
197 if loc = Locator.parse(word.strip)
207 def find_objects_for_index
208 # Omit manifest_text from index results unless expressly selected.
209 @select ||= model_class.api_accessible_attributes(:user).
210 map { |attr_spec| attr_spec.first.to_s } - ["manifest_text"]
214 def find_object_by_uuid
216 if !@object and !params[:uuid].match(/^[0-9a-f]+\+\d+$/)
217 # Normalize the given uuid and search again.
218 hash_part = params[:uuid].match(/^([0-9a-f]*)/)[1]
219 collection = Collection.where('uuid like ?', hash_part + '+%').first
221 # We know the collection exists, and what its real uuid is in
222 # the database. Now, throw out @objects and repeat the usual
223 # lookup procedure. (Returning the collection at this point
224 # would bypass permission checks.)
226 @where = { uuid: collection.uuid }
227 find_objects_for_index
228 @object = @objects.first
233 def munge_manifest_locators(manifest, &block)
234 self.class.munge_manifest_locators(manifest, &block)
237 def sign_manifests(*manifests)
238 if current_api_client_authorization
240 key: Rails.configuration.blob_signing_key,
241 api_token: current_api_client_authorization.api_token,
242 ttl: Rails.configuration.blob_signing_ttl,
244 manifests.each do |text|
245 munge_manifest_locators(text) do |loc|
246 Blob.sign_locator(loc.to_s, signing_opts)