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.
17 api_token = current_api_client_authorization.andand.api_token
18 signing_opts = { key: Rails.configuration.permission_key, api_token: api_token }
19 resource_attrs[:manifest_text].lines.each do |entry|
20 # TODO(twp): fail the request if this match fails.
21 # Add in Phase 4 (see #2755)
22 m = /([[:xdigit:]]{32}(\+[[:digit:]]+)?)(\+A\S*)?/.match(entry)
25 logger.warn "No API token present; cannot verify signature #{m[0]}"
27 elsif !Blob.verify_signature m[0], signing_opts
28 logger.warn "Invalid signature on locator #{m[0]}"
34 raise ArvadosModel::PermissionDeniedError
37 # Remove any permission signatures from the manifest.
38 resource_attrs[:manifest_text]
39 .gsub!(/^(\S+\s+)([[:xdigit:]]{32}(\+[[:digit:]]+)?)(\+A\S*)/, '\1\2')
41 # Save the collection with the stripped manifest.
43 @object = model_class.new resource_attrs.reject { |k,v| k == :owner_uuid }
46 rescue ActiveRecord::RecordNotUnique
47 logger.debug resource_attrs.inspect
48 if resource_attrs[:manifest_text] and resource_attrs[:uuid]
49 @existing_object = model_class.
50 where('uuid=? and manifest_text=?',
51 resource_attrs[:uuid],
52 resource_attrs[:manifest_text]).
54 @object = @existing_object || @object
60 owner_uuid: owner_uuid,
61 link_class: 'permission',
63 head_uuid: @object.uuid,
66 ActiveRecord::Base.transaction do
67 if Link.where(link_attrs).empty?
68 Link.create! link_attrs
77 if current_api_client_authorization
79 key: Rails.configuration.permission_key,
80 api_token: current_api_client_authorization.api_token,
82 @object[:manifest_text]
83 .gsub!(/^(\S+\s+)([[:xdigit:]]{32}(\+[[:digit:]]+)?)/) { |m|
84 $1 + Blob.sign_locator($2, signing_opts)
87 render json: @object.as_api_response(:with_data)
90 def collection_uuid(uuid)
91 m = /([a-f0-9]{32}(\+[0-9]+)?)(\+.*)?/.match(uuid)
99 def script_param_edges(visited, sp)
103 script_param_edges(visited, v)
107 script_param_edges(visited, v)
111 m = collection_uuid(sp)
113 generate_provenance_edges(visited, m)
118 def generate_provenance_edges(visited, uuid)
119 m = collection_uuid(uuid)
122 if not uuid or uuid.empty? or visited[uuid]
126 logger.debug "visiting #{uuid}"
129 # uuid is a collection
130 Collection.readable_by(current_user).where(uuid: uuid).each do |c|
131 visited[uuid] = c.as_api_response
132 visited[uuid][:files] = []
134 visited[uuid][:files] << f
138 Job.readable_by(current_user).where(output: uuid).each do |job|
139 generate_provenance_edges(visited, job.uuid)
142 Job.readable_by(current_user).where(log: uuid).each do |job|
143 generate_provenance_edges(visited, job.uuid)
147 # uuid is something else
148 rsc = ArvadosModel::resource_class_for_uuid uuid
150 Job.readable_by(current_user).where(uuid: uuid).each do |job|
151 visited[uuid] = job.as_api_response
152 script_param_edges(visited, job.script_parameters)
155 rsc.where(uuid: uuid).each do |r|
156 visited[uuid] = r.as_api_response
161 Link.readable_by(current_user).
162 where(head_uuid: uuid, link_class: "provenance").
164 visited[link.uuid] = link.as_api_response
165 generate_provenance_edges(visited, link.tail_uuid)
168 #puts "finished #{uuid}"
173 generate_provenance_edges(visited, @object[:uuid])
177 def generate_used_by_edges(visited, uuid)
178 m = collection_uuid(uuid)
181 if not uuid or uuid.empty? or visited[uuid]
185 logger.debug "visiting #{uuid}"
188 # uuid is a collection
189 Collection.readable_by(current_user).where(uuid: uuid).each do |c|
190 visited[uuid] = c.as_api_response
191 visited[uuid][:files] = []
193 visited[uuid][:files] << f
197 if uuid == "d41d8cd98f00b204e9800998ecf8427e+0"
198 # special case for empty collection
202 Job.readable_by(current_user).where(["jobs.script_parameters like ?", "%#{uuid}%"]).each do |job|
203 generate_used_by_edges(visited, job.uuid)
207 # uuid is something else
208 rsc = ArvadosModel::resource_class_for_uuid uuid
210 Job.readable_by(current_user).where(uuid: uuid).each do |job|
211 visited[uuid] = job.as_api_response
212 generate_used_by_edges(visited, job.output)
215 rsc.where(uuid: uuid).each do |r|
216 visited[uuid] = r.as_api_response
221 Link.readable_by(current_user).
222 where(tail_uuid: uuid, link_class: "provenance").
224 visited[link.uuid] = link.as_api_response
225 generate_used_by_edges(visited, link.head_uuid)
228 #puts "finished #{uuid}"
233 generate_used_by_edges(visited, @object[:uuid])
238 def find_object_by_uuid
240 if !@object and !params[:uuid].match(/^[0-9a-f]+\+\d+$/)
241 # Normalize the given uuid and search again.
242 hash_part = params[:uuid].match(/^([0-9a-f]*)/)[1]
243 collection = Collection.where('uuid like ?', hash_part + '+%').first
245 # We know the collection exists, and what its real uuid is in
246 # the database. Now, throw out @objects and repeat the usual
247 # lookup procedure. (Returning the collection at this point
248 # would bypass permission checks.)
250 @where = { uuid: collection.uuid }
251 find_objects_for_index
252 @object = @objects.first