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 if resource_attrs[:uuid] and (loc = Locator.parse(resource_attrs[:uuid]))
9 resource_attrs[:portable_data_hash] = loc.to_s
10 resource_attrs.delete :uuid
13 # Check permissions on the collection manifest.
14 # If any signature cannot be verified, return 403 Permission denied.
15 api_token = current_api_client_authorization.andand.api_token
17 key: Rails.configuration.blob_signing_key,
19 ttl: Rails.configuration.blob_signing_ttl,
21 resource_attrs[:manifest_text].lines.each do |entry|
22 entry.split[1..-1].each do |tok|
23 if /^[[:digit:]]+:[[:digit:]]+:/.match tok
24 # This is a filename token, not a blob locator. Note that we
25 # keep checking tokens after this, even though manifest
26 # format dictates that all subsequent tokens will also be
27 # filenames. Safety first!
28 elsif Blob.verify_signature tok, signing_opts
30 elsif Locator.parse(tok).andand.signature
31 # Signature provided, but verify_signature did not like it.
32 logger.warn "Invalid signature on locator #{tok}"
33 raise ArvadosModel::PermissionDeniedError
34 elsif Rails.configuration.permit_create_collection_with_unsigned_manifest
35 # No signature provided, but we are running in insecure mode.
36 logger.debug "Missing signature on locator #{tok} ignored"
37 elsif Blob.new(tok).empty?
38 # No signature provided -- but no data to protect, either.
40 logger.warn "Missing signature on locator #{tok}"
41 raise ArvadosModel::PermissionDeniedError
46 # Remove any permission signatures from the manifest.
47 munge_manifest_locators(resource_attrs[:manifest_text]) do |loc|
48 loc.without_signature.to_s
54 def find_object_by_uuid
55 if loc = Locator.parse(params[:id])
57 if c = Collection.readable_by(*@read_users).where({ portable_data_hash: loc.to_s }).limit(1).first
59 uuid: c.portable_data_hash,
60 portable_data_hash: c.portable_data_hash,
61 manifest_text: c.manifest_text,
63 data_size: c.data_size
73 sign_manifests(@object[:manifest_text])
74 if @object.is_a? Collection
75 render json: @object.as_api_response
82 sign_manifests(*@objects.map { |c| c[:manifest_text] })
86 def script_param_edges(visited, sp)
90 script_param_edges(visited, v)
94 script_param_edges(visited, v)
98 if loc = Locator.parse(sp)
99 search_edges(visited, loc.to_s, :search_up)
104 def search_edges(visited, uuid, direction)
105 if uuid.nil? or uuid.empty? or visited[uuid]
109 if loc = Locator.parse(uuid)
111 return if visited[loc.to_s]
114 logger.debug "visiting #{uuid}"
117 # uuid is a portable_data_hash
118 if c = Collection.readable_by(*@read_users).where(portable_data_hash: loc.to_s).limit(1).first
119 visited[loc.to_s] = {
120 portable_data_hash: c.portable_data_hash,
122 data_size: c.data_size
126 if direction == :search_up
127 # Search upstream for jobs where this locator is the output of some job
128 Job.readable_by(*@read_users).where(output: loc.to_s).each do |job|
129 search_edges(visited, job.uuid, :search_up)
132 Job.readable_by(*@read_users).where(log: loc.to_s).each do |job|
133 search_edges(visited, job.uuid, :search_up)
135 elsif direction == :search_down
136 if loc.to_s == "d41d8cd98f00b204e9800998ecf8427e+0"
137 # Special case, don't follow the empty collection.
141 # Search downstream for jobs where this locator is in script_parameters
142 Job.readable_by(*@read_users).where(["jobs.script_parameters like ?", "%#{loc.to_s}%"]).each do |job|
143 search_edges(visited, job.uuid, :search_down)
147 # uuid is a regular Arvados UUID
148 rsc = ArvadosModel::resource_class_for_uuid uuid
150 Job.readable_by(*@read_users).where(uuid: uuid).each do |job|
151 visited[uuid] = job.as_api_response
152 if direction == :search_up
153 # Follow upstream collections referenced in the script parameters
154 script_param_edges(visited, job.script_parameters)
155 elsif direction == :search_down
156 # Follow downstream job output
157 search_edges(visited, job.output, direction)
160 elsif rsc == Collection
161 if c = Collection.readable_by(*@read_users).where(uuid: uuid).limit(1).first
162 search_edges(visited, c.portable_data_hash, direction)
163 visited[c.portable_data_hash] = c.as_api_response
166 rsc.where(uuid: uuid).each do |r|
167 visited[uuid] = r.as_api_response
172 if direction == :search_up
173 # Search for provenance links pointing to the current uuid
174 Link.readable_by(*@read_users).
175 where(head_uuid: uuid, link_class: "provenance").
177 visited[link.uuid] = link.as_api_response
178 search_edges(visited, link.tail_uuid, direction)
180 elsif direction == :search_down
181 # Search for provenance links emanating from the current uuid
182 Link.readable_by(current_user).
183 where(tail_uuid: uuid, link_class: "provenance").
185 visited[link.uuid] = link.as_api_response
186 search_edges(visited, link.head_uuid, direction)
193 search_edges(visited, @object[:uuid] || @object[:portable_data_hash], :search_up)
199 search_edges(visited, @object[:uuid] || @object[:portable_data_hash], :search_down)
203 def self.munge_manifest_locators(manifest)
204 # Given a manifest text and a block, yield each locator,
205 # and replace it with whatever the block returns.
206 manifest.andand.gsub!(/ [[:xdigit:]]{32}(\+[[:digit:]]+)?(\+\S+)/) do |word|
207 if loc = Locator.parse(word.strip)
217 def find_objects_for_index
218 # Omit manifest_text from index results unless expressly selected.
220 @select = model_class.api_accessible_attributes(:user).map { |attr_spec|attr_spec.first.to_s } - ["manifest_text"]
225 def munge_manifest_locators(manifest, &block)
226 self.class.munge_manifest_locators(manifest, &block)
229 def sign_manifests(*manifests)
230 if current_api_client_authorization
232 key: Rails.configuration.blob_signing_key,
233 api_token: current_api_client_authorization.api_token,
234 ttl: Rails.configuration.blob_signing_ttl,
236 manifests.each do |text|
237 munge_manifest_locators(text) do |loc|
238 Blob.sign_locator(loc.to_s, signing_opts)