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
19 key: Rails.configuration.blob_signing_key,
21 ttl: Rails.configuration.blob_signing_ttl,
23 resource_attrs[:manifest_text].lines.each do |entry|
24 entry.split[1..-1].each do |tok|
25 # TODO(twp): in Phase 4, fail the request if the locator
26 # lacks a permission signature. (see #2755)
27 loc = Locator.parse(tok)
28 if loc and loc.signature
30 logger.warn "No API token present; cannot verify signature on #{loc}"
32 elsif !Blob.verify_signature tok, signing_opts
33 logger.warn "Invalid signature on locator #{loc}"
40 raise ArvadosModel::PermissionDeniedError
43 # Remove any permission signatures from the manifest.
44 resource_attrs[:manifest_text]
45 .gsub!(/ [[:xdigit:]]{32}(\+[[:digit:]]+)?(\+\S+)/) { |word|
47 loc = Locator.parse(word)
49 " " + loc.without_signature.to_s
55 # Save the collection with the stripped manifest.
57 @object = model_class.new resource_attrs.reject { |k,v| k == :owner_uuid }
60 rescue ActiveRecord::RecordNotUnique
61 logger.debug resource_attrs.inspect
62 if resource_attrs[:manifest_text] and resource_attrs[:uuid]
63 @existing_object = model_class.
64 where('uuid=? and manifest_text=?',
65 resource_attrs[:uuid],
66 resource_attrs[:manifest_text]).
68 @object = @existing_object || @object
73 owner_uuid: owner_uuid,
74 link_class: 'permission',
76 head_uuid: @object.uuid,
79 ActiveRecord::Base.transaction do
80 if Link.where(link_attrs).empty?
81 Link.create! link_attrs
90 if current_api_client_authorization
92 key: Rails.configuration.blob_signing_key,
93 api_token: current_api_client_authorization.api_token,
94 ttl: Rails.configuration.blob_signing_ttl,
96 @object[:manifest_text]
97 .gsub!(/ [[:xdigit:]]{32}(\+[[:digit:]]+)?(\+\S+)/) { |word|
99 loc = Locator.parse(word)
101 " " + Blob.sign_locator(word, signing_opts)
107 render json: @object.as_api_response(:with_data)
110 def collection_uuid(uuid)
111 m = /([a-f0-9]{32}(\+[0-9]+)?)(\+.*)?/.match(uuid)
119 def script_param_edges(visited, sp)
123 script_param_edges(visited, v)
127 script_param_edges(visited, v)
131 m = collection_uuid(sp)
133 generate_provenance_edges(visited, m)
138 def generate_provenance_edges(visited, uuid)
139 m = collection_uuid(uuid)
142 if not uuid or uuid.empty? or visited[uuid]
146 logger.debug "visiting #{uuid}"
149 # uuid is a collection
150 Collection.readable_by(current_user).where(uuid: uuid).each do |c|
151 visited[uuid] = c.as_api_response
152 visited[uuid][:files] = []
154 visited[uuid][:files] << f
158 Job.readable_by(current_user).where(output: uuid).each do |job|
159 generate_provenance_edges(visited, job.uuid)
162 Job.readable_by(current_user).where(log: uuid).each do |job|
163 generate_provenance_edges(visited, job.uuid)
167 # uuid is something else
168 rsc = ArvadosModel::resource_class_for_uuid uuid
170 Job.readable_by(current_user).where(uuid: uuid).each do |job|
171 visited[uuid] = job.as_api_response
172 script_param_edges(visited, job.script_parameters)
175 rsc.where(uuid: uuid).each do |r|
176 visited[uuid] = r.as_api_response
181 Link.readable_by(current_user).
182 where(head_uuid: uuid, link_class: "provenance").
184 visited[link.uuid] = link.as_api_response
185 generate_provenance_edges(visited, link.tail_uuid)
188 #puts "finished #{uuid}"
193 generate_provenance_edges(visited, @object[:uuid])
197 def generate_used_by_edges(visited, uuid)
198 m = collection_uuid(uuid)
201 if not uuid or uuid.empty? or visited[uuid]
205 logger.debug "visiting #{uuid}"
208 # uuid is a collection
209 Collection.readable_by(current_user).where(uuid: uuid).each do |c|
210 visited[uuid] = c.as_api_response
211 visited[uuid][:files] = []
213 visited[uuid][:files] << f
217 if uuid == "d41d8cd98f00b204e9800998ecf8427e+0"
218 # special case for empty collection
222 Job.readable_by(current_user).where(["jobs.script_parameters like ?", "%#{uuid}%"]).each do |job|
223 generate_used_by_edges(visited, job.uuid)
227 # uuid is something else
228 rsc = ArvadosModel::resource_class_for_uuid uuid
230 Job.readable_by(current_user).where(uuid: uuid).each do |job|
231 visited[uuid] = job.as_api_response
232 generate_used_by_edges(visited, job.output)
235 rsc.where(uuid: uuid).each do |r|
236 visited[uuid] = r.as_api_response
241 Link.readable_by(current_user).
242 where(tail_uuid: uuid, link_class: "provenance").
244 visited[link.uuid] = link.as_api_response
245 generate_used_by_edges(visited, link.head_uuid)
248 #puts "finished #{uuid}"
253 generate_used_by_edges(visited, @object[:uuid])
258 def find_object_by_uuid
260 if !@object and !params[:uuid].match(/^[0-9a-f]+\+\d+$/)
261 # Normalize the given uuid and search again.
262 hash_part = params[:uuid].match(/^([0-9a-f]*)/)[1]
263 collection = Collection.where('uuid like ?', hash_part + '+%').first
265 # We know the collection exists, and what its real uuid is in
266 # the database. Now, throw out @objects and repeat the usual
267 # lookup procedure. (Returning the collection at this point
268 # would bypass permission checks.)
270 @where = { uuid: collection.uuid }
271 find_objects_for_index
272 @object = @objects.first