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 resource_attrs[:manifest_text]
43 .gsub!(/ [[:xdigit:]]{32}(\+[[:digit:]]+)?(\+\S+)/) { |word|
45 loc = Locator.parse(word)
47 " " + loc.without_signature.to_s
56 def find_object_by_uuid
57 if loc = Locator.parse(params[:id])
59 if c = Collection.readable_by(*@read_users).where({ portable_data_hash: loc.to_s }).limit(1).first
61 portable_data_hash: c.portable_data_hash,
62 manifest_text: c.manifest_text,
64 data_size: c.data_size
74 if current_api_client_authorization
76 key: Rails.configuration.blob_signing_key,
77 api_token: current_api_client_authorization.api_token,
78 ttl: Rails.configuration.blob_signing_ttl,
80 @object[:manifest_text]
81 .gsub!(/ [[:xdigit:]]{32}(\+[[:digit:]]+)?(\+\S+)/) { |word|
83 loc = Locator.parse(word)
85 " " + Blob.sign_locator(word, signing_opts)
91 if @object.is_a? Collection
92 render json: @object.as_api_response(:with_data)
98 def script_param_edges(visited, sp)
102 script_param_edges(visited, v)
106 script_param_edges(visited, v)
110 if loc = Locator.parse(sp)
111 search_edges(visited, loc.to_s, :search_up)
116 def search_edges(visited, uuid, direction)
117 if uuid.nil? or uuid.empty? or visited[uuid]
121 if loc = Locator.parse(uuid)
123 return if visited[loc.to_s]
126 logger.debug "visiting #{uuid}"
129 # uuid is a portable_data_hash
130 if c = Collection.readable_by(*@read_users).where(portable_data_hash: loc.to_s).limit(1).first
131 visited[loc.to_s] = {
132 portable_data_hash: c.portable_data_hash,
134 data_size: c.data_size
138 if direction == :search_up
139 # Search upstream for jobs where this locator is the output of some job
140 Job.readable_by(*@read_users).where(output: loc.to_s).each do |job|
141 search_edges(visited, job.uuid, :search_up)
144 Job.readable_by(*@read_users).where(log: loc.to_s).each do |job|
145 search_edges(visited, job.uuid, :search_up)
147 elsif direction == :search_down
148 if loc.to_s == "d41d8cd98f00b204e9800998ecf8427e+0"
149 # Special case, don't follow the empty collection.
153 # Search downstream for jobs where this locator is in script_parameters
154 Job.readable_by(*@read_users).where(["jobs.script_parameters like ?", "%#{loc.to_s}%"]).each do |job|
155 search_edges(visited, job.uuid, :search_down)
159 # uuid is a regular Arvados UUID
160 rsc = ArvadosModel::resource_class_for_uuid uuid
162 Job.readable_by(*@read_users).where(uuid: uuid).each do |job|
163 visited[uuid] = job.as_api_response
164 if direction == :search_up
165 # Follow upstream collections referenced in the script parameters
166 script_param_edges(visited, job.script_parameters)
167 elsif direction == :search_down
168 # Follow downstream job output
169 search_edges(visited, job.output, direction)
172 elsif rsc == Collection
173 if c = Collection.readable_by(*@read_users).where(uuid: uuid).limit(1).first
174 search_edges(visited, c.portable_data_hash, direction)
175 visited[c.portable_data_hash] = c.as_api_response
178 rsc.where(uuid: uuid).each do |r|
179 visited[uuid] = r.as_api_response
184 if direction == :search_up
185 # Search for provenance links pointing to the current uuid
186 Link.readable_by(*@read_users).
187 where(head_uuid: uuid, link_class: "provenance").
189 visited[link.uuid] = link.as_api_response
190 search_edges(visited, link.tail_uuid, direction)
192 elsif direction == :search_down
193 # Search for provenance links emanating from the current uuid
194 Link.readable_by(current_user).
195 where(tail_uuid: uuid, link_class: "provenance").
197 visited[link.uuid] = link.as_api_response
198 search_edges(visited, link.head_uuid, direction)
205 search_edges(visited, @object[:uuid] || @object[:portable_data_hash], :search_up)
211 search_edges(visited, @object[:uuid] || @object[:portable_data_hash], :search_down)