1 class Arvados::V1::CollectionsController < ApplicationController
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 unless current_user.can? write: owner_uuid
10 logger.warn "User #{current_user.andand.uuid} tried to set collection owner_uuid to #{owner_uuid}"
11 raise ArvadosModel::PermissionDeniedError
14 # Check permissions on the collection manifest.
15 # If any signature cannot be verified, return 403 Permission denied.
16 api_token = current_api_client_authorization.andand.api_token
18 key: Rails.configuration.blob_signing_key,
20 ttl: Rails.configuration.blob_signing_ttl,
22 resource_attrs[:manifest_text].lines.each do |entry|
23 entry.split[1..-1].each do |tok|
24 if /^[[:digit:]]+:[[:digit:]]+:/.match tok
25 # This is a filename token, not a blob locator. Note that we
26 # keep checking tokens after this, even though manifest
27 # format dictates that all subsequent tokens will also be
28 # filenames. Safety first!
29 elsif Blob.verify_signature tok, signing_opts
31 elsif Locator.parse(tok).andand.signature
32 # Signature provided, but verify_signature did not like it.
33 logger.warn "Invalid signature on locator #{tok}"
34 raise ArvadosModel::PermissionDeniedError
35 elsif Rails.configuration.permit_create_collection_with_unsigned_manifest
36 # No signature provided, but we are running in insecure mode.
37 logger.debug "Missing signature on locator #{tok} ignored"
38 elsif Blob.new(tok).empty?
39 # No signature provided -- but no data to protect, either.
41 logger.warn "Missing signature on locator #{tok}"
42 raise ArvadosModel::PermissionDeniedError
47 # Remove any permission signatures from the manifest.
48 munge_manifest_locators(resource_attrs[:manifest_text]) do |loc|
49 loc.without_signature.to_s
52 # Save the collection with the stripped manifest.
54 @object = model_class.new resource_attrs.reject { |k,v| k == :owner_uuid }
57 rescue ActiveRecord::RecordNotUnique
58 logger.debug resource_attrs.inspect
59 if @object.manifest_text and @object.uuid
60 @existing_object = model_class.
61 where('uuid=? and manifest_text=?',
63 @object.manifest_text).
65 @object = @existing_object || @object
70 owner_uuid: owner_uuid,
71 link_class: 'permission',
73 head_uuid: @object.uuid,
76 ActiveRecord::Base.transaction do
77 if Link.where(link_attrs).empty?
78 Link.create! link_attrs
87 sign_manifests(@object[:manifest_text])
92 sign_manifests(*@objects.map { |c| c[:manifest_text] })
96 def collection_uuid(uuid)
97 m = /([a-f0-9]{32}(\+[0-9]+)?)(\+.*)?/.match(uuid)
105 def script_param_edges(visited, sp)
109 script_param_edges(visited, v)
113 script_param_edges(visited, v)
117 m = collection_uuid(sp)
119 generate_provenance_edges(visited, m)
124 def generate_provenance_edges(visited, uuid)
125 m = collection_uuid(uuid)
128 if not uuid or uuid.empty? or visited[uuid]
132 logger.debug "visiting #{uuid}"
135 # uuid is a collection
136 Collection.readable_by(current_user).where(uuid: uuid).each do |c|
137 visited[uuid] = c.as_api_response
138 visited[uuid][:files] = []
140 visited[uuid][:files] << f
144 Job.readable_by(current_user).where(output: uuid).each do |job|
145 generate_provenance_edges(visited, job.uuid)
148 Job.readable_by(current_user).where(log: uuid).each do |job|
149 generate_provenance_edges(visited, job.uuid)
153 # uuid is something else
154 rsc = ArvadosModel::resource_class_for_uuid uuid
156 Job.readable_by(current_user).where(uuid: uuid).each do |job|
157 visited[uuid] = job.as_api_response
158 script_param_edges(visited, job.script_parameters)
161 rsc.where(uuid: uuid).each do |r|
162 visited[uuid] = r.as_api_response
167 Link.readable_by(current_user).
168 where(head_uuid: uuid, link_class: "provenance").
170 visited[link.uuid] = link.as_api_response
171 generate_provenance_edges(visited, link.tail_uuid)
174 #puts "finished #{uuid}"
179 generate_provenance_edges(visited, @object[:uuid])
183 def generate_used_by_edges(visited, uuid)
184 m = collection_uuid(uuid)
187 if not uuid or uuid.empty? or visited[uuid]
191 logger.debug "visiting #{uuid}"
194 # uuid is a collection
195 Collection.readable_by(current_user).where(uuid: uuid).each do |c|
196 visited[uuid] = c.as_api_response
197 visited[uuid][:files] = []
199 visited[uuid][:files] << f
203 if uuid == "d41d8cd98f00b204e9800998ecf8427e+0"
204 # special case for empty collection
208 Job.readable_by(current_user).where(["jobs.script_parameters like ?", "%#{uuid}%"]).each do |job|
209 generate_used_by_edges(visited, job.uuid)
213 # uuid is something else
214 rsc = ArvadosModel::resource_class_for_uuid uuid
216 Job.readable_by(current_user).where(uuid: uuid).each do |job|
217 visited[uuid] = job.as_api_response
218 generate_used_by_edges(visited, job.output)
221 rsc.where(uuid: uuid).each do |r|
222 visited[uuid] = r.as_api_response
227 Link.readable_by(current_user).
228 where(tail_uuid: uuid, link_class: "provenance").
230 visited[link.uuid] = link.as_api_response
231 generate_used_by_edges(visited, link.head_uuid)
234 #puts "finished #{uuid}"
239 generate_used_by_edges(visited, @object[:uuid])
243 def self.munge_manifest_locators(manifest)
244 # Given a manifest text and a block, yield each locator,
245 # and replace it with whatever the block returns.
246 manifest.andand.gsub!(/ [[:xdigit:]]{32}(\+[[:digit:]]+)?(\+\S+)/) do |word|
247 if loc = Locator.parse(word.strip)
257 def find_objects_for_index
258 # Omit manifest_text from index results unless expressly selected.
259 @select ||= model_class.api_accessible_attributes(:user).
260 map { |attr_spec| attr_spec.first.to_s } - ["manifest_text"]
264 def find_object_by_uuid
266 if !@object and !params[:uuid].match(/^[0-9a-f]+\+\d+$/)
267 # Normalize the given uuid and search again.
268 hash_part = params[:uuid].match(/^([0-9a-f]*)/)[1]
269 collection = Collection.where('uuid like ?', hash_part + '+%').first
271 # We know the collection exists, and what its real uuid is in
272 # the database. Now, throw out @objects and repeat the usual
273 # lookup procedure. (Returning the collection at this point
274 # would bypass permission checks.)
276 @where = { uuid: collection.uuid }
277 find_objects_for_index
278 @object = @objects.first
283 def munge_manifest_locators(manifest, &block)
284 self.class.munge_manifest_locators(manifest, &block)
287 def sign_manifests(*manifests)
288 if current_api_client_authorization
290 key: Rails.configuration.blob_signing_key,
291 api_token: current_api_client_authorization.api_token,
292 ttl: Rails.configuration.blob_signing_ttl,
294 manifests.each do |text|
295 munge_manifest_locators(text) do |loc|
296 Blob.sign_locator(loc.to_s, signing_opts)