var container; // element that receives new content
var src; // url for retrieving content
var scrollHeight;
+ var spinner, colspan;
scrollHeight = scroller.scrollHeight || $('body')[0].scrollHeight;
if ($(scroller).scrollTop() + $(scroller).height()
>
return;
// Don't start another request until this one finishes
$(container).attr('data-infinite-content-href', null);
- $(container).append('<img src="/assets/ajax-loader.gif" class="infinite-scroller-spinner"></img>');
+ spinner = '<div class="spinner spinner-32px spinner-h-center"></div>';
+ if ($(container).is('table,tbody,thead,tfoot')) {
+ // Hack to determine how many columns a new tr should have
+ // in order to reach full width.
+ colspan = $(container).closest('table').
+ find('tr').eq(0).find('td,th').length;
+ if (colspan == 0)
+ colspan = '*';
+ spinner = ('<tr class="spinner"><td colspan="' + colspan + '">' +
+ spinner +
+ '</td></tr>');
+ }
+ $(container).append(spinner);
$.ajax(src,
{dataType: 'json',
type: 'GET',
data: {},
context: {container: container, src: src}}).
+ always(function() {
+ $(this.container).find(".spinner").detach();
+ }).
fail(function(jqxhr, status, error) {
if (jqxhr.readyState == 0 || jqxhr.status == 0) {
message = "Cancelled."
$(this.container).attr('data-infinite-content-href', this.src);
}).
done(function(data, status, jqxhr) {
- $(this.container).find(".infinite-scroller-spinner").detach();
$(this.container).append(data.content);
$(this.container).attr('data-infinite-content-href', data.next_page_href);
});
$('[data-infinite-scroller]').each(function() {
var $scroller = $($(this).attr('data-infinite-scroller'));
if (!$scroller.hasClass('smart-scroll') &&
- 'scroll' != $scroller.css('overflow-y'))
+ 'scroll' != $scroller.css('overflow-y'))
$scroller = $(window);
$scroller.
addClass('infinite-scroller').
prop('disabled', !any);
if ($this.hasClass('active')) {
- $(".modal-dialog-preview-pane").html('<img src="/assets/ajax-loader.gif"></img>');
+ $(".modal-dialog-preview-pane").html('<div class="spinner spinner-32px spinner-h-center spinner-v-center"></div>');
$.ajax($this.attr('data-preview-href'),
{dataType: "html"}).
done(function(data, status, jqxhr) {
opacity: 0;
}
+.spinner {
+ /* placeholder for stuff like $.find('.spinner').detach() */
+}
+
+.spinner-32px {
+ background-image: url('<%= asset_path('spinner_32px.gif') %>');
+ background-repeat: no-repeat;
+ width: 32px;
+ height: 32px;
+}
+
+.spinner-h-center {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.spinner-v-center {
+ position: relative;
+ top: 45%;
+}
+
.rotating {
color: #f00;
/* Chrome and Firefox, at least in Linux, render a horrible shaky
render_error status: 422
end
- def render_error(opts)
- opts = {status: 500}.merge opts
+ def render_error(opts={})
+ opts[:status] ||= 500
respond_to do |f|
# json must come before html here, so it gets used as the
# default format when js is requested by the client. This lets
# ajax:error callback parse the response correctly, even though
# the browser can't.
f.json { render opts.merge(json: {success: false, errors: @errors}) }
- f.html { render opts.merge(controller: 'application', action: 'error') }
+ f.html { render({action: 'error'}.merge(opts)) }
end
end
def render_exception(e)
logger.error e.inspect
logger.error e.backtrace.collect { |x| x + "\n" }.join('') if e.backtrace
- if @object.andand.errors.andand.full_messages.andand.any?
+ err_opts = {status: 422}
+ if e.is_a?(ArvadosApiClient::ApiError)
+ err_opts.merge!(action: 'api_error', locals: {api_error: e})
+ @errors = e.api_response[:errors]
+ elsif @object.andand.errors.andand.full_messages.andand.any?
@errors = @object.errors.full_messages
else
@errors = [e.to_s]
end
- if e.is_a? ArvadosApiClient::NotLoggedInException
- self.render_error status: 422
- else
- set_thread_api_token do
- self.render_error status: 422
- end
+ # If the user has an active session, and the API server is available,
+ # make user information available on the error page.
+ begin
+ load_api_token(session[:arvados_api_token])
+ rescue ArvadosApiClient::ApiError
+ load_api_token(nil)
+ end
+ # Preload projects trees for the template. If that fails, set empty
+ # trees so error page rendering can proceed. (It's easier to rescue the
+ # exception here than in a template.)
+ begin
+ build_project_trees
+ rescue ArvadosApiClient::ApiError
+ @my_project_tree ||= []
+ @shared_project_tree ||= []
end
+ render_error(err_opts)
end
def render_not_found(e=ActionController::RoutingError.new("Path not found"))
logger.error e.inspect
@errors = ["Path not found"]
set_thread_api_token do
- self.render_error status: 404
+ self.render_error(action: '404', status: 404)
end
end
end
def current_user
- return Thread.current[:user] if Thread.current[:user]
-
- if Thread.current[:arvados_api_token]
- if session[:user]
- if session[:user][:is_active] != true
- Thread.current[:user] = User.current
- else
- Thread.current[:user] = User.new(session[:user])
- end
- else
- Thread.current[:user] = User.current
- end
- else
- logger.error "No API token in Thread"
- return nil
- end
+ Thread.current[:user]
end
def model_class
[:arvados_api_token, :user].each do |key|
start_values[key] = Thread.current[key]
end
- Thread.current[:arvados_api_token] = api_token
- Thread.current[:user] = nil
+ load_api_token(api_token)
begin
yield
ensure
if params[:id] and params[:id].match /\D/
params[:uuid] = params.delete :id
end
- if not model_class
- @object = nil
- elsif params[:uuid].is_a? String
- if params[:uuid].empty?
+ begin
+ if not model_class
@object = nil
+ elsif not params[:uuid].is_a?(String)
+ @object = model_class.where(uuid: params[:uuid]).first
+ elsif params[:uuid].empty?
+ @object = nil
+ elsif (model_class != Link and
+ resource_class_for_uuid(params[:uuid]) == Link)
+ @name_link = Link.find(params[:uuid])
+ @object = model_class.find(@name_link.head_uuid)
else
- if (model_class != Link and
- resource_class_for_uuid(params[:uuid]) == Link)
- @name_link = Link.find(params[:uuid])
- @object = model_class.find(@name_link.head_uuid)
- else
- @object = model_class.find(params[:uuid])
- end
+ @object = model_class.find(params[:uuid])
end
- else
- @object = model_class.where(uuid: params[:uuid]).first
+ rescue ArvadosApiClient::NotFoundException, RuntimeError => error
+ if error.is_a?(RuntimeError) and (error.message !~ /^argument to find\(/)
+ raise
+ end
+ render_not_found(error)
+ return false
end
end
def thread_clear
- Thread.current[:arvados_api_token] = nil
- Thread.current[:user] = nil
+ load_api_token(nil)
Rails.cache.delete_matched(/^request_#{Thread.current.object_id}_/)
yield
Rails.cache.delete_matched(/^request_#{Thread.current.object_id}_/)
end
+ # Set up the thread with the given API token and associated user object.
+ def load_api_token(new_token)
+ Thread.current[:arvados_api_token] = new_token
+ if new_token.nil?
+ Thread.current[:user] = nil
+ elsif (new_token == session[:arvados_api_token]) and
+ session[:user].andand[:is_active]
+ Thread.current[:user] = User.new(session[:user])
+ else
+ Thread.current[:user] = User.current
+ end
+ end
+
+ # If there's a valid api_token parameter, set up the session with that
+ # user's information. Return true if the method redirects the request
+ # (usually a post-login redirect); false otherwise.
+ def setup_user_session
+ return false unless params[:api_token]
+ Thread.current[:arvados_api_token] = params[:api_token]
+ begin
+ user = User.current
+ rescue ArvadosApiClient::NotLoggedInException
+ false # We may redirect to login, or not, based on the current action.
+ else
+ session[:arvados_api_token] = params[:api_token]
+ session[:user] = {
+ uuid: user.uuid,
+ email: user.email,
+ first_name: user.first_name,
+ last_name: user.last_name,
+ is_active: user.is_active,
+ is_admin: user.is_admin,
+ prefs: user.prefs
+ }
+ if !request.format.json? and request.method.in? ['GET', 'HEAD']
+ # Repeat this request with api_token in the (new) session
+ # cookie instead of the query string. This prevents API
+ # tokens from appearing in (and being inadvisedly copied
+ # and pasted from) browser Location bars.
+ redirect_to strip_token_from_path(request.fullpath)
+ true
+ else
+ false
+ end
+ ensure
+ Thread.current[:arvados_api_token] = nil
+ end
+ end
+
# Save the session API token in thread-local storage, and yield.
# This method also takes care of session setup if the request
# provides a valid api_token parameter.
# If a token is unavailable or expired, the block is still run, with
# a nil token.
def set_thread_api_token
- # If an API token has already been found, pass it through.
if Thread.current[:arvados_api_token]
- yield
+ yield # An API token has already been found - pass it through.
return
+ elsif setup_user_session
+ return # A new session was set up and received a response.
end
begin
- # If there's a valid api_token parameter, use it to set up the session.
- if (Thread.current[:arvados_api_token] = params[:api_token]) and
- verify_api_token
- session[:arvados_api_token] = params[:api_token]
- u = User.current
- session[:user] = {
- uuid: u.uuid,
- email: u.email,
- first_name: u.first_name,
- last_name: u.last_name,
- is_active: u.is_active,
- is_admin: u.is_admin,
- prefs: u.prefs
- }
- if !request.format.json? and request.method.in? ['GET', 'HEAD']
- # Repeat this request with api_token in the (new) session
- # cookie instead of the query string. This prevents API
- # tokens from appearing in (and being inadvisedly copied
- # and pasted from) browser Location bars.
- redirect_to strip_token_from_path(request.fullpath)
- return
- end
- end
-
- # With setup done, handle the request using the session token.
- Thread.current[:arvados_api_token] = session[:arvados_api_token]
- begin
+ load_api_token(session[:arvados_api_token])
+ yield
+ rescue ArvadosApiClient::NotLoggedInException
+ # If we got this error with a token, it must've expired.
+ # Retry the request without a token.
+ unless Thread.current[:arvados_api_token].nil?
+ load_api_token(nil)
yield
- rescue ArvadosApiClient::NotLoggedInException
- # If we got this error with a token, it must've expired.
- # Retry the request without a token.
- unless Thread.current[:arvados_api_token].nil?
- Thread.current[:arvados_api_token] = nil
- yield
- end
end
ensure
# Remove token in case this Thread is used for anything else.
- Thread.current[:arvados_api_token] = nil
+ load_api_token(nil)
end
end
end
end
- def verify_api_token
- begin
- Link.where(uuid: 'just-verifying-my-api-token')
- true
- rescue ArvadosApiClient::NotLoggedInException
- false
- end
- end
-
def ensure_current_user_is_admin
unless current_user and current_user.is_admin
@errors = ['Permission denied']
def show_file_links
Thread.current[:reader_tokens] = [params[:reader_token]]
- find_object_by_uuid
+ return if false.equal?(find_object_by_uuid)
render layout: false
end
# error we encounter, and return nil.
most_specific_error = [401]
token_list.each do |api_token|
- using_specific_api_token(api_token) do
- begin
+ begin
+ using_specific_api_token(api_token) do
yield
return api_token
- rescue ArvadosApiClient::NotLoggedInException => error
- status = 401
- rescue => error
- status = (error.message =~ /\[API: (\d+)\]$/) ? $1.to_i : nil
- raise unless [401, 403, 404].include?(status)
end
- if status >= most_specific_error.first
- most_specific_error = [status, error]
+ rescue ArvadosApiClient::ApiError => error
+ if error.api_status >= most_specific_error.first
+ most_specific_error = [error.api_status, error]
end
end
end
else
link_name = object_for_dataclass(resource_class, link_uuid).andand.friendly_link_name
end
- rescue RuntimeError
+ rescue ArvadosApiClient::NotFoundException
# If that lookup failed, the link will too. So don't make one.
return attrvalue
end
require 'thread'
class ArvadosApiClient
- class NotLoggedInException < StandardError
+ class ApiError < StandardError
+ attr_reader :api_response, :api_response_s, :api_status, :request_url
+
+ def initialize(request_url, errmsg)
+ @request_url = request_url
+ @api_response ||= {}
+ super(errmsg)
+ end
end
- class InvalidApiResponseException < StandardError
+
+ class NoApiResponseException < ApiError
+ def initialize(request_url, exception)
+ @api_response_s = exception.to_s
+ super(request_url,
+ "#{exception.class.to_s} error connecting to API server")
+ end
end
- class AccessForbiddenException < StandardError
+
+ class InvalidApiResponseException < ApiError
+ def initialize(request_url, api_response)
+ @api_status = api_response.status_code
+ @api_response_s = api_response.content
+ super(request_url, "Unparseable response from API server")
+ end
+ end
+
+ class ApiErrorResponseException < ApiError
+ def initialize(request_url, api_response)
+ @api_status = api_response.status_code
+ @api_response_s = api_response.content
+ @api_response = Oj.load(@api_response_s, :symbol_keys => true)
+ errors = @api_response[:errors]
+ if errors.respond_to?(:join)
+ errors = errors.join("\n\n")
+ else
+ errors = errors.to_s
+ end
+ super(request_url, "#{errors} [API: #{@api_status}]")
+ end
end
+ class AccessForbiddenException < ApiErrorResponseException; end
+ class NotFoundException < ApiErrorResponseException; end
+ class NotLoggedInException < ApiErrorResponseException; end
+
+ ERROR_CODE_CLASSES = {
+ 401 => NotLoggedInException,
+ 403 => AccessForbiddenException,
+ 404 => NotFoundException,
+ }
+
@@profiling_enabled = Rails.configuration.profiling_enabled
@@discovery = nil
profile_checkpoint { "Prepare request #{url} #{query[:uuid]} #{query[:where]} #{query[:filters]}" }
msg = @client_mtx.synchronize do
- @api_client.post(url,
- query,
- header: header)
+ begin
+ @api_client.post(url, query, header: header)
+ rescue => exception
+ raise NoApiResponseException.new(url, exception)
+ end
end
profile_checkpoint 'API transaction'
- if msg.status_code == 401
- raise NotLoggedInException.new
- end
-
- json = msg.content
-
begin
- resp = Oj.load(json, :symbol_keys => true)
+ resp = Oj.load(msg.content, :symbol_keys => true)
rescue Oj::ParseError
- raise InvalidApiResponseException.new json
+ resp = nil
end
if not resp.is_a? Hash
- raise InvalidApiResponseException.new json
- end
- if msg.status_code != 200
- errors = resp[:errors]
- errors = errors.join("\n\n") if errors.is_a? Array
- if msg.status_code == 403
- raise AccessForbiddenException.new "#{errors} [API: #{msg.status_code}]"
- else
- raise "#{errors} [API: #{msg.status_code}]"
- end
+ raise InvalidApiResponseException.new(url, msg)
+ elsif msg.status_code != 200
+ error_class = ERROR_CODE_CLASSES.fetch(msg.status_code, ApiError)
+ raise error_class.new(url, msg)
end
+
if resp[:_profile]
Rails.logger.info "API client: " \
"#{resp.delete(:_profile)[:request_time]} request_time"
end
def self.columns
- return @columns unless @columns.nil?
+ return @columns if @columns.andand.any?
@columns = []
@attribute_info ||= {}
schema = arvados_api_client.discovery[:schemas][self.to_s.to_sym]
--- /dev/null
+<%
+ if (controller.andand.action_name == 'show') and params[:uuid]
+ class_name = controller.model_class.to_s.underscore.humanize(capitalize: false)
+ req_item = safe_join([class_name, " with UUID ",
+ raw("<code>"), params[:uuid], raw("</code>")], "")
+ else
+ req_item = "page you requested"
+ end
+%>
+
+<h2>Not Found</h2>
+
+<p>The <%= req_item %> was not found.
+
+<% if class_name %>
+Perhaps you'd like to
+<%= link_to("browse all #{class_name.pluralize}", action: :index) %>?
+<% end %>
+
+</p>
+
--- /dev/null
+{"errors":<%= raw @errors.to_json %>}
\ No newline at end of file
attr('data-method', '<%= j params[:action_method] %>').
data('action-data', <%= raw params[:action_data] %>);
$(".chooser-show-project").on("click", function() {
- $("#choose-scroll").html("<%=j image_tag 'ajax-loader.gif' %>");
+ $("#choose-scroll").html("<div class=\"spinner spinner-32px spinner-h-center\"></div>");
$(".modal-dialog-preview-pane").html('');
var t = $(this);
var d = {
<%= render(partial: 'show_' + pane.downcase,
locals: { comparable: comparable, objects: @objects }) %>
<% else %>
- <%= image_tag 'ajax-loader.gif' %>
+ <div class="spinner spinner-32px spinner-h-center"></div>
<% end %>
</div>
</div>
--- /dev/null
+<h2>Oh... fiddlesticks.</h2>
+
+<p>An error occurred when Workbench sent a request to the Arvados API server. Try reloading this page. If the problem is temporary, your request might go through next time.
+
+<% if not api_error %>
+</p>
+<% else %>
+If that doesn't work, the information below can help system administrators track down the problem.
+</p>
+
+<dl>
+ <dt>API request URL</dt>
+ <dd><code><%= api_error.request_url %></code></dd>
+
+ <% if api_error.api_response.empty? %>
+ <dt>Invalid API response</dt>
+ <dd><%= api_error.api_response_s %></dd>
+ <% else %>
+ <dt>API response</dt>
+ <dd><pre><%= Oj.dump(api_error.api_response, indent: 2) %></pre></dd>
+ <% end %>
+</dl>
+<% end %>
--- /dev/null
+{"errors":<%= raw @errors.to_json %>}
\ No newline at end of file
<% end %>
</td>
<td>
- <%= raw(distance_of_time_in_words(c.created_at, Time.now).sub('about ','~').sub(' ',' ')) if c.created_at %>
+ <%= c.created_at.to_s if c.created_at %>
</td>
<td>
<% current_state = @collection_info[c.uuid][:wanted_by_me] ? 'persistent' : 'cache' %>
<th></th>
<th>uuid</th>
<th>contents</th>
- <th>age</th>
+ <th>created at</th>
<th>storage</th>
<th>tags</th>
</tr>
<% logcollection = Collection.find @object.log %>
<% if logcollection %>
$.ajax('<%=j url_for logcollection %>/<%=j logcollection.files[0][1] %>').
- done(function(data, status, jqxhr) {
- logViewer.filter();
- addToLogViewer(logViewer, data.split("\n"), taskState);
- logViewer.filter(makeFilter());
- generateJobOverview("#log-viewer-overview", logViewer, taskState);
- $("#logloadspinner").detach();
- }).
- fail(function(jqxhr, status, error) {
- $("#logloadspinner").detach();
- });
+ done(function(data, status, jqxhr) {
+ logViewer.filter();
+ addToLogViewer(logViewer, data.split("\n"), taskState);
+ logViewer.filter(makeFilter());
+ generateJobOverview("#log-viewer-overview", logViewer, taskState);
+ $("#log-viewer .spinner").detach();
+ }).
+ fail(function(jqxhr, status, error) {
+ $("#log-viewer .spinner").detach();
+ });
<% end %>
<% else %>
<%# Live log loading not implemented yet. %>
</table>
<% if @object.log and logcollection %>
- <%= image_tag 'ajax-loader.gif', id: "logloadspinner" %>
+ <div class="spinner spinner-32px"></div>
<% end %>
</div>
</li>
-->
- <% if current_user %>
<li class="dropdown notification-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" id="notifications-menu">
<span class="badge badge-alert notification-count"><%= @notification_count %></span>
<% end %>
</ul>
</li>
- <% end %>
<li class="dropdown selection-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
</a>
<ul class="dropdown-menu" role="menu">
<li role="presentation" class="dropdown-header">
- System tools
+ Settings
</li>
<li role="presentation"><a href="/repositories">
<i class="fa fa-lg fa-code-fork fa-fw"></i> Repositories
<% else %>
<li><a href="<%= arvados_api_client.arvados_login_url(return_to: root_url) %>">Log in</a></li>
<% end %>
+
+ <li class="dropdown help-menu">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="arv-help">
+ <span class="fa fa-lg fa-question-circle"></span>
+ </a>
+ <ul class="dropdown-menu">
+ <li><%= link_to raw('<i class="fa fa-book fa-fw"></i> Tutorials and User guide'), "#{Rails.configuration.arvados_docsite}/user", target: "_blank" %></li>
+ <li><%= link_to raw('<i class="fa fa-book fa-fw"></i> API Reference'), "#{Rails.configuration.arvados_docsite}/api", target: "_blank" %></li>
+ <li><%= link_to raw('<i class="fa fa-book fa-fw"></i> SDK Reference'), "#{Rails.configuration.arvados_docsite}/sdk", target: "_blank" %></li>
+ </ul>
+ </li>
</ul>
</div><!-- /.navbar-collapse -->
</nav>
</th><th>
Owner
</th><th>
- Age
+ Created at
</th><th>
</th>
</tr>
</td><td>
<%= link_to_if_arvados_object ob.owner_uuid, friendly_name: true %>
</td><td>
- <%= distance_of_time_in_words(ob.created_at, Time.now) %>
+ <%= ob.created_at.to_s %>
</td><td>
<%= render partial: 'delete_object_button', locals: {object:ob} %>
</td>
<% content_for :tab_line_buttons do %>
- <%= form_tag '/pipeline_instances' do |f| %>
- <%= hidden_field :pipeline_instance, :pipeline_template_uuid, :value => @object.uuid %>
- <%= button_tag "Run this pipeline", {class: 'btn btn-primary pull-right', id: "run-pipeline-button"} %>
-<% end %>
+ <%= button_to(choose_projects_path(id: "run-pipeline-button",
+ title: 'Choose project',
+ editable: true,
+ action_name: 'Choose',
+ action_href: pipeline_instances_path,
+ action_method: 'post',
+ action_data: {selection_param: 'pipeline_instance[owner_uuid]',
+ 'pipeline_instance[pipeline_template_uuid]' => @object.uuid,
+ 'success' => 'redirect-to-created-object'
+ }.to_json),
+ { class: "btn btn-primary btn-sm", remote: true, method: 'get' }
+ ) do %>
+ Run this pipeline
+ <% end %>
<% end %>
<%= render partial: 'pipeline_instances/show_components_editable', locals: {editable: false} %>
<tr>
<td>
- <%= form_tag '/pipeline_instances' do |f| %>
- <%= hidden_field :pipeline_instance, :pipeline_template_uuid, :value => ob.uuid %>
- <%= button_tag nil, {class: "btn btn-default btn-xs", title: "Run #{ob.name}"} do %>
- Run <i class="fa fa-fw fa-play"></i>
- <% end %>
- <% end %>
+ <%= button_to(choose_projects_path(id: "run-pipeline-button",
+ title: 'Choose project',
+ editable: true,
+ action_name: 'Choose',
+ action_href: pipeline_instances_path,
+ action_method: 'post',
+ action_data: {selection_param: 'pipeline_instance[owner_uuid]',
+ 'pipeline_instance[pipeline_template_uuid]' => ob.uuid,
+ 'success' => 'redirect-to-created-object'
+ }.to_json),
+ { class: "btn btn-default btn-xs", title: "Run #{ob.name}", remote: true, method: 'get' }
+ ) do %>
+ <i class="fa fa-fw fa-play"></i> Run
+ <% end %>
</td>
<td>
<%= render :partial => "show_object_button", :locals => {object: ob, size: 'xs'} %>
<th>Script</th>
<th>Output</th>
<th>Log</th>
- <th>Age</th>
+ <th>Created at</th>
<th>Status</th>
<th>Progress</th>
</tr>
<td>
<small>
- <%= raw(distance_of_time_in_words(j.created_at, Time.now).sub('about ','~').sub(' ',' ')) if j.created_at %>
+ <%= j.created_at.to_s if j.created_at %>
</small>
</td>
<tr>
<th>Instance</th>
<th>Template</th>
- <th>Age</th>
+ <th>Created at</th>
<th>Status</th>
<th>Progress</th>
</tr>
<td>
<small>
- <%= raw(distance_of_time_in_words(p.created_at, Time.now).sub('about ','~').sub(' ',' ')) if p.created_at %>
+ <%= (p.created_at.to_s) if p.created_at %>
</small>
</td>
</td>
<td>
<small>
- <%= raw(distance_of_time_in_words(c.created_at, Time.now).sub('about ','~').sub(' ',' ')) if c.created_at %>
+ <%= c.created_at.to_s if c.created_at %>
</small>
</td>
<td>
assert users.size == 3, 'Expected two objects in the preloaded hash'
end
+ test "requesting a nonexistent object returns 404" do
+ # We're really testing ApplicationController's find_object_by_uuid.
+ # It's easiest to do that by instantiating a concrete controller.
+ @controller = NodesController.new
+ get(:show, {id: "zzzzz-zzzzz-zzzzzzzzzzzzzzz"}, session_for(:admin))
+ assert_response 404
+ end
end
require 'test_helper'
class CollectionsControllerTest < ActionController::TestCase
+ NONEXISTENT_COLLECTION = "ffffffffffffffffffffffffffffffff+0"
+
def collection_params(collection_name, file_name=nil)
uuid = api_fixture('collections')[collection_name.to_s]['uuid']
params = {uuid: uuid, id: uuid}
"when showing the user agreement.")
assert_response :success
end
+
+ test "requesting nonexistent Collection returns 404" do
+ show_collection({uuid: NONEXISTENT_COLLECTION, id: NONEXISTENT_COLLECTION},
+ :active, 404)
+ end
end
end
test "ignore previously valid token (for deleted user), don't crash" do
- get :welcome, {}, session_for(:valid_token_deleted_user)
+ get :activity, {}, session_for(:valid_token_deleted_user)
assert_response :redirect
assert_match /^#{Rails.configuration.arvados_login_base}/, @response.redirect_url
assert_nil assigns(:my_jobs)
require 'integration_helper'
class ErrorsTest < ActionDispatch::IntegrationTest
- BAD_UUID = "zzzzz-zzzzz-zzzzzzzzzzzzzzz"
+ BAD_UUID = "ffffffffffffffffffffffffffffffff+0"
test "error page renders user navigation" do
visit(page_with_token("active", "/collections/#{BAD_UUID}"))
- assert(page.has_text?(@@API_AUTHS["active"]["email"]),
+ assert(page.has_text?(api_fixture("users")["active"]["email"]),
"User information missing from error page")
assert(page.has_no_text?(/log ?in/i),
"Logged in user prompted to log in on error page")
end
+ test "no user navigation with expired token" do
+ visit(page_with_token("expired", "/collections/#{BAD_UUID}"))
+ assert(page.has_no_text?(api_fixture("users")["active"]["email"]),
+ "Page visited with expired token included user information")
+ assert(page.has_selector?("a", text: /log ?in/i),
+ "Login prompt missing on expired token error page")
+ end
+
test "error page renders without login" do
visit "/collections/download/#{BAD_UUID}/#{@@API_AUTHS['active']['api_token']}"
assert(page.has_no_text?(/\b500\b/),
"Error page without login returned 500")
end
+
+ test "'object not found' page includes search link" do
+ visit(page_with_token("active", "/collections/#{BAD_UUID}"))
+ assert(all("a").any? { |a| a[:href] =~ %r{/collections/?(\?|$)} },
+ "no search link found on 404 page")
+ end
+
+ def now_timestamp
+ Time.now.utc.to_i
+ end
+
+ def page_has_error_token?(start_stamp)
+ matching_stamps = (start_stamp .. now_timestamp).to_a.join("|")
+ # Check the page HTML because we really don't care how it's presented.
+ # I think it would even be reasonable to put it in a comment.
+ page.html =~ /\b(#{matching_stamps})\+[0-9A-Fa-f]{8}\b/
+ end
+
+ # We use API tokens with limited scopes as the quickest way to get the API
+ # server to return an error. If Workbench gets smarter about coping when
+ # it has a too-limited token, these tests will need to be adjusted.
+ test "API error page includes error token" do
+ start_stamp = now_timestamp
+ visit(page_with_token("active_readonly", "/authorized_keys"))
+ click_on "Add a new authorized key"
+ assert(page.has_text?(/fiddlesticks/i),
+ "Not on an error page after making an SSH key out of scope")
+ assert(page_has_error_token?(start_stamp), "no error token on 404 page")
+ end
+
+ test "showing a bad UUID returns 404" do
+ visit(page_with_token("active", "/pipeline_templates/zzz"))
+ assert(page.has_no_text?(/fiddlesticks/i),
+ "trying to show a bad UUID rendered a fiddlesticks page, not 404")
+ end
+
+ test "404 page includes information about missing object" do
+ visit(page_with_token("active", "/groups/zazazaz"))
+ assert(page.has_text?(/group with UUID zazazaz/i),
+ "name of searched group missing from 404 page")
+ end
+
+ test "unrouted 404 page works" do
+ visit(page_with_token("active", "/__asdf/ghjk/zxcv"))
+ assert(page.has_text?(/not found/i),
+ "unrouted page missing 404 text")
+ assert(page.has_no_text?(/fiddlesticks/i),
+ "unrouted request returned a generic error page, not 404")
+ end
end
find('a,button', text: 'Run').click
end
+ # project chooser
+ within('.modal-dialog') do
+ find('.selectable', text: 'A Project').click
+ find('button', text: 'Choose').click
+ end
+
# This pipeline needs input. So, Run should be disabled
page.assert_selector 'a.disabled,button.disabled', text: 'Run'
instance_page = current_path
- # put this pipeline instance in "A Project"
- find('button', text: 'Choose a project...').click
- within('.modal-dialog') do
- find('.selectable', text: 'A Project').click
- find('button', text: 'Move').click
- end
-
# Go over to the collections page and select something
visit '/collections'
within('tr', text: 'GNU_General_Public_License') do
JOBS_DEPS = jobs/Dockerfile
+BWA_SAMTOOLS_DEPS = bwa-samtools/Dockerfile
+
API_DEPS = api/Dockerfile $(API_GENERATED)
DOC_DEPS = doc/Dockerfile doc/apache2_vhost
$(DOCKER_BUILD) -t arvados/jobs jobs
date >jobs-image
+bwa-samtools-image: jobs-image $(BUILD) $(BWA_SAMTOOLS_DEPS)
+ $(DOCKER_BUILD) -t arvados/jobs-bwa-samtools bwa-samtools
+ date >bwa-samtools-image
+
workbench-image: passenger-image $(BUILD) $(WORKBENCH_DEPS)
mkdir -p workbench/generated
tar -czf workbench/generated/workbench.tar.gz -C build/apps workbench
--- /dev/null
+FROM arvados/jobs
+MAINTAINER Peter Amstutz <peter.amstutz@curoverse.com>
+
+USER root
+
+RUN cd /tmp && \
+ curl --location http://downloads.sourceforge.net/project/bio-bwa/bwa-0.7.9a.tar.bz2 -o bwa-0.7.9a.tar.bz2 && \
+ tar xjf bwa-0.7.9a.tar.bz2 && \
+ cd bwa-0.7.9a && \
+ make && \
+ (find . -executable -type f -print0 | xargs -0 -I {} mv {} /usr/local/bin) && \
+ rm -r /tmp/bwa-0.7.9a* && \
+ cd /tmp && \
+ curl --location http://downloads.sourceforge.net/project/samtools/samtools/0.1.19/samtools-0.1.19.tar.bz2 -o samtools-0.1.19.tar.bz2 && \
+ tar xjf samtools-0.1.19.tar.bz2 && \
+ cd samtools-0.1.19 && \
+ make && \
+ (find . -executable -type f -print0 | xargs -0 -I {} mv {} /usr/local/bin) && \
+ rm -r /tmp/samtools-0.1.19*
+
+USER crunch
\ No newline at end of file
return @discovery_documents["#{api}:#{version}"] ||=
begin
# fetch new API discovery doc if stale
- cached_doc = File.expand_path '~/.cache/arvados/discovery_uri.json'
- if not File.exist?(cached_doc) or (Time.now - File.mtime(cached_doc)) > 86400
+ cached_doc = File.expand_path '~/.cache/arvados/discovery_uri.json' rescue nil
+
+ if cached_doc.nil? or not File.exist?(cached_doc) or (Time.now - File.mtime(cached_doc)) > 86400
response = self.execute!(:http_method => :get,
:uri => self.discovery_uri(api, version),
:authenticated => false)
- FileUtils.makedirs(File.dirname cached_doc)
- File.open(cached_doc, 'w') do |f|
- f.puts response.body
+
+ begin
+ FileUtils.makedirs(File.dirname cached_doc)
+ File.open(cached_doc, 'w') do |f|
+ f.puts response.body
+ end
+ rescue
+ return JSON.load response.body
end
end
def init_config
# read authentication data from arvados configuration file if present
lineno = 0
- config_file = File.expand_path('~/.config/arvados/settings.conf')
- if File.exist? config_file then
+ config_file = File.expand_path('~/.config/arvados/settings.conf') rescue nil
+ if not config_file.nil? and File.exist? config_file then
File.open(config_file, 'r').each do |line|
lineno = lineno + 1
# skip comments
$ENV{"TASK_SLOT_NODE"} = $slot[$childslot]->{node}->{name};
$ENV{"TASK_SLOT_NUMBER"} = $slot[$childslot]->{cpu};
$ENV{"TASK_WORK"} = $ENV{"JOB_WORK"}."/$id.$$";
+ $ENV{"HOME"} = $ENV{"TASK_WORK"};
$ENV{"TASK_KEEPMOUNT"} = $ENV{"TASK_WORK"}.".keep";
$ENV{"TASK_TMPDIR"} = $ENV{"TASK_WORK"}; # deprecated
$ENV{"CRUNCH_NODE_SLOTS"} = $slot[$childslot]->{node}->{ncpus};
$command .= "-v \Q$ENV{TASK_WORK}:/tmp/crunch-job:rw\E ";
$command .= "-v \Q$ENV{CRUNCH_SRC}:/tmp/crunch-src:ro\E ";
$command .= "-v \Q$ENV{TASK_KEEPMOUNT}:/mnt:ro\E ";
+ $command .= "-e \QHOME=/tmp/crunch-job\E ";
while (my ($env_key, $env_val) = each %ENV)
{
if ($env_key =~ /^(ARVADOS|JOB|TASK)_/) {
gem 'themes_for_rails'
-gem 'arvados-cli', '>= 0.1.20140627084759'
+gem 'arvados-cli', '>= 0.1.20140630151639'
# pg_power lets us use partial indexes in schema.rb in Rails 3
gem 'pg_power'
addressable (2.3.6)
andand (1.3.3)
arel (3.0.3)
- arvados (0.1.20140627084759)
+ arvados (0.1.20140630151639)
activesupport (>= 3.2.13)
andand
google-api-client (~> 0.6.3)
json (>= 1.7.7)
- arvados-cli (0.1.20140627084759)
+ arvados-cli (0.1.20140630151639)
activesupport (~> 3.2, >= 3.2.13)
andand (~> 1.3, >= 1.3.3)
arvados (~> 0.1.0)
DEPENDENCIES
acts_as_api
andand
- arvados-cli (>= 0.1.20140627084759)
+ arvados-cli (>= 0.1.20140630151639)
coffee-rails (~> 3.2.0)
database_cleaner
faye-websocket
include LoadParam
include RecordFilters
- ERROR_ACTIONS = [:render_error, :render_not_found]
-
-
respond_to :json
protect_from_forgery
+ ERROR_ACTIONS = [:render_error, :render_not_found]
+
before_filter :respond_with_json_by_default
before_filter :remote_ip
before_filter :load_read_auths
attr_accessor :resource_attrs
+ begin
+ rescue_from(Exception,
+ ArvadosModel::PermissionDeniedError,
+ :with => :render_error)
+ rescue_from(ActiveRecord::RecordNotFound,
+ ActionController::RoutingError,
+ ActionController::UnknownController,
+ AbstractController::ActionNotFound,
+ :with => :render_not_found)
+ end
+
def index
@objects.uniq!(&:id) if @select.nil? or @select.include? "id"
if params[:eager] and params[:eager] != '0' and params[:eager] != 0 and params[:eager] != ''
end
end
- begin
- rescue_from Exception,
- :with => :render_error
- rescue_from ActiveRecord::RecordNotFound,
- :with => :render_not_found
- rescue_from ActionController::RoutingError,
- :with => :render_not_found
- rescue_from ActionController::UnknownController,
- :with => :render_not_found
- rescue_from AbstractController::ActionNotFound,
- :with => :render_not_found
- rescue_from ArvadosModel::PermissionDeniedError,
- :with => :render_error
- end
-
def render_404_if_no_object
render_not_found "Object not found" if !@object
end
errors = [e.inspect]
end
status = e.respond_to?(:http_status) ? e.http_status : 422
- render json: { errors: errors }, status: status
+ send_error(*errors, status: status)
end
def render_not_found(e=ActionController::RoutingError.new("Path not found"))
logger.error e.inspect
- render json: { errors: ["Path not found"] }, status: 404
+ send_error("Path not found", status: 404)
end
protected
+ def send_error(*args)
+ if args.last.is_a? Hash
+ err = args.pop
+ else
+ err = {}
+ end
+ err[:errors] ||= args
+ err[:error_token] = [Time.now.utc.to_i, "%08x" % rand(16 ** 8)].join("+")
+ status = err.delete(:status) || 422
+ logger.error "Error #{err[:error_token]}: #{status}"
+ render json: err, status: status
+ end
+
def find_objects_for_index
@objects ||= model_class.readable_by(*@read_users)
apply_where_limit_order_params
def require_login
if not current_user
respond_to do |format|
- format.json {
- render :json => { errors: ['Not logged in'] }.to_json, status: 401
- }
- format.html {
- redirect_to '/auth/joshid'
- }
+ format.json { send_error("Not logged in", status: 401) }
+ format.html { redirect_to '/auth/joshid' }
end
false
end
def admin_required
unless current_user and current_user.is_admin
- render :json => { errors: ['Forbidden'] }.to_json, status: 403
+ send_error("Forbidden", status: 403)
end
end
def require_auth_scope
if @read_auths.empty?
if require_login != false
- render :json => { errors: ['Forbidden'] }.to_json, status: 403
+ send_error("Forbidden", status: 403)
end
false
end
def current_api_client_is_trusted
unless Thread.current[:api_client].andand.is_trusted
- render :json => { errors: ['Forbidden: this API client cannot manipulate other clients\' access tokens.'] }.to_json, status: 403
+ send_error('Forbidden: this API client cannot manipulate other clients\' access tokens.',
+ status: 403)
end
end
end
def create
[:repository, :script, :script_version, :script_parameters].each do |r|
if !resource_attrs[r]
- return render json: {
- :errors => ["#{r} attribute must be specified"]
- }, status: :unprocessable_entity
+ return send_error("#{r} attribute must be specified",
+ status: :unprocessable_entity)
end
end
def check_uuid_kind uuid, kind
if kind and ArvadosModel::resource_class_for_uuid(uuid).andand.kind != kind
- render :json => { errors: ["'#{kind}' does not match uuid '#{uuid}', expected '#{ArvadosModel::resource_class_for_uuid(uuid).andand.kind}'"] }.to_json, status: 422
+ send_error("'#{kind}' does not match uuid '#{uuid}', expected '#{ArvadosModel::resource_class_for_uuid(uuid).andand.kind}'",
+ status: 422)
nil
else
true
end
def groups_i_can(verb)
- self.group_permissions.select { |uuid, mask| mask[verb] }.keys
+ my_groups = self.group_permissions.select { |uuid, mask| mask[verb] }.keys
+ if verb == :read
+ my_groups << anonymous_group_uuid
+ end
+ my_groups
end
def can?(actions)
--- /dev/null
+class AnonymousGroup < ActiveRecord::Migration
+ include CurrentApiClient
+
+ def up
+ # create the anonymous group and user
+ anonymous_group
+ anonymous_user
+ end
+
+ def down
+ act_as_system_user do
+ anonymous_user.destroy
+ anonymous_group.destroy
+ end
+ end
+
+end
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20140611173003) do
+ActiveRecord::Schema.define(:version => 20140627210837) do
#
# It is invoked by `rake db:seed` and `rake db:setup`.
-# These two methods would create the system user and group objects on
-# demand later anyway, but it's better form to create them up front.
+# These two methods would create these objects on demand
+# later anyway, but it's better form to create them up front.
include CurrentApiClient
system_user
system_group
+anonymous_group
+anonymous_user
'000000000000000'].join('-')
end
+ def anonymous_group_uuid
+ [Server::Application.config.uuid_prefix,
+ Group.uuid_prefix,
+ 'anonymouspublic'].join('-')
+ end
+
+ def anonymous_user_uuid
+ [Server::Application.config.uuid_prefix,
+ User.uuid_prefix,
+ 'anonymouspublic'].join('-')
+ end
+
def system_user
if not $system_user
real_current_user = Thread.current[:user]
Thread.current[:user] = system_user
end
end
+
+ def anonymous_group
+ if not $anonymous_group
+ act_as_system_user do
+ ActiveRecord::Base.transaction do
+ $anonymous_group = Group.
+ where(uuid: anonymous_group_uuid).first_or_create do |g|
+ g.update_attributes(name: "Anonymous group",
+ description: "Anonymous group")
+ end
+ end
+ end
+ end
+ $anonymous_group
+ end
+
+ def anonymous_user
+ if not $anonymous_user
+ act_as_system_user do
+ $anonymous_user = User.where('uuid=?', anonymous_user_uuid).first
+ if !$anonymous_user
+ $anonymous_user = User.new(uuid: anonymous_user_uuid,
+ is_active: false,
+ is_admin: false,
+ email: 'anonymouspublic',
+ first_name: 'anonymouspublic',
+ last_name: 'anonymouspublic')
+ $anonymous_user.save!
+ $anonymous_user.reload
+ end
+
+ group_perms = Link.where(tail_uuid: anonymous_user_uuid,
+ head_uuid: anonymous_group_uuid,
+ link_class: 'permission',
+ name: 'can_read')
+
+ if !group_perms.any?
+ group_perm = Link.create!(tail_uuid: anonymous_user_uuid,
+ head_uuid: anonymous_group_uuid,
+ link_class: 'permission',
+ name: 'can_read')
+ end
+ end
+ end
+ $anonymous_user
+ end
+
end
next
end
- $stderr.puts `cd #{arvados_internal.shellescape} && git fetch --no-tags #{src_repo.shellescape} && git tag #{job.uuid.shellescape} #{job.script_version.shellescape}`
+ $stderr.puts `cd #{arvados_internal.shellescape} && git fetch-pack --all #{src_repo.shellescape} && git tag #{job.uuid.shellescape} #{job.script_version.shellescape}`
cmd_args << crunch_job_bin
cmd_args << '--job-api-token'
--- /dev/null
+#!/usr/bin/env ruby
+
+# Get or Create an anonymous user token.
+# If get option is used, an existing anonymous user token is returned. If none exist, one is created.
+# If the get option is omitted, a new token is created and returned.
+
+require 'trollop'
+
+opts = Trollop::options do
+ banner ''
+ banner "Usage: get_anonymous_user_token "
+ banner ''
+ opt :get, <<-eos
+Get an existing anonymous user token. If no such token exists \
+or if this option is omitted, a new token is created and returned.
+ eos
+end
+
+get_existing = opts[:get]
+
+require File.dirname(__FILE__) + '/../config/environment'
+
+include ApplicationHelper
+act_as_system_user
+
+def create_api_client_auth
+ api_client_auth = ApiClientAuthorization.
+ new(user: anonymous_user,
+ api_client_id: 0,
+ expires_at: Time.now + 100.years,
+ scopes: ['GET /'])
+ api_client_auth.save!
+ api_client_auth.reload
+end
+
+if get_existing
+ api_client_auth = ApiClientAuthorization.
+ where('user_id=?', anonymous_user.id.to_i).
+ where('expires_at>?', Time.now).
+ select { |auth| auth.scopes == ['GET /'] }.
+ first
+end
+
+# either not a get or no api_client_auth was found
+if !api_client_auth
+ api_client_auth = create_api_client_auth
+end
+
+# print it to the console
+puts api_client_auth.api_token
scopes: ["GET /arvados/v1/api_client_authorizations",
"POST /arvados/v1/api_client_authorizations"]
+active_readonly:
+ api_client: untrusted
+ user: active
+ api_token: activereadonlyabcdefghijklmnopqrstuvwxyz1234568790
+ expires_at: 2038-01-01 00:00:00
+ scopes: ["GET /"]
+
spectator:
api_client: untrusted
user: spectator
user_id: 1234567
api_token: tewfa58099sndckyqhlgd37za6e47o6h03r9l1vpll23hudm8b
expires_at: 2038-01-01 00:00:00
+
+anonymous:
+ api_client: untrusted
+ user: anonymous
+ api_token: 4kg6k6lzmp9kj4cpkcoxie964cmvjahbt4fod9zru44k4jqdmi
+ expires_at: 2038-01-01 00:00:00
+ scopes: ["GET /"]
modified_at: 2014-05-03 18:50:08 -0400
updated_at: 2014-05-03 18:50:08 -0400
name: Owned by bad group a
+
+anonymous_group:
+ uuid: zzzzz-j7d0g-anonymouspublic
+ owner_uuid: zzzzz-tpzed-000000000000000
+ name: Anonymous group
+ description: Anonymous group
+
+anonymously_accessible_project:
+ uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
+ owner_uuid: zzzzz-tpzed-d9tiejq69daie8f
+ name: Unrestricted public data
+ group_class: project
+ description: An anonymously accessible project
head_uuid: ~
properties: {}
+anonymous_group_can_read_anonymously_accessible_project:
+ uuid: zzzzz-o0j2j-15gpzezqjg4bc4z
+ owner_uuid: zzzzz-tpzed-000000000000000
+ created_at: 2014-05-30 14:30:00.184389725 Z
+ modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+ modified_by_user_uuid: zzzzz-tpzed-000000000000000
+ modified_at: 2014-05-30 14:30:00.184019565 Z
+ updated_at: 2014-05-30 14:30:00.183829316 Z
+ link_class: permission
+ name: can_read
+ tail_uuid: zzzzz-j7d0g-anonymouspublic
+ head_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
+ properties: {}
+
+user_agreement_in_anonymously_accessible_project:
+ uuid: zzzzz-o0j2j-k0ukddp35mt6ok1
+ owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
+ created_at: 2014-06-13 20:42:26 -0800
+ modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+ modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
+ modified_at: 2014-06-13 20:42:26 -0800
+ updated_at: 2014-06-13 20:42:26 -0800
+ link_class: name
+ name: GNU General Public License, version 3
+ tail_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
+ head_uuid: b519d9cb706a29fc7ea24dbea2f05851+249025
+ properties: {}
+
+user_agreement_readable_by_anonymously_accessible_project:
+ uuid: zzzzz-o0j2j-o5ds5gvhkztdc8h
+ owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
+ created_at: 2014-06-13 20:42:26 -0800
+ modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+ modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
+ modified_at: 2014-06-13 20:42:26 -0800
+ updated_at: 2014-06-13 20:42:26 -0800
+ link_class: permission
+ name: can_read
+
active_user_permission_to_docker_image_collection:
uuid: zzzzz-o0j2j-dp1d8395ldqw33s
owner_uuid: zzzzz-tpzed-000000000000000
is_admin: false
prefs: {}
+anonymous:
+ uuid: zzzzz-tpzed-anonymouspublic
+ email: anonymouspublic
+ first_name: anonymouspublic
+ last_name: anonymouspublic
+ is_active: false
+ is_admin: false
+ prefs: {}
--- /dev/null
+require 'test_helper'
+
+class ApplicationControllerTest < ActionController::TestCase
+ BAD_UUID = "zzzzz-zzzzz-zzzzzzzzzzzzzzz"
+
+ def now_timestamp
+ Time.now.utc.to_i
+ end
+
+ setup do
+ # These tests are meant to check behavior in ApplicationController.
+ # We instantiate a small concrete controller for convenience.
+ @controller = Arvados::V1::SpecimensController.new
+ @start_stamp = now_timestamp
+ end
+
+ def check_error_token
+ token = json_response['error_token']
+ assert_not_nil token
+ token_time = token.split('+', 2).first.to_i
+ assert_operator(token_time, :>=, @start_stamp, "error token too old")
+ assert_operator(token_time, :<=, now_timestamp, "error token too new")
+ end
+
+ def check_404(errmsg="Path not found")
+ assert_response 404
+ assert_equal([errmsg], json_response['errors'])
+ check_error_token
+ end
+
+ test "requesting nonexistent object returns 404 error" do
+ authorize_with :admin
+ get(:show, id: BAD_UUID)
+ check_404
+ end
+
+ test "requesting object without read permission returns 404 error" do
+ authorize_with :spectator
+ get(:show, id: specimens(:owned_by_active_user).uuid)
+ check_404
+ end
+
+ test "submitting bad object returns error" do
+ authorize_with :spectator
+ post(:create, specimen: {badattr: "badvalue"})
+ assert_response 422
+ check_error_token
+ end
+end
assert @uninvited_user.can? :write=>"#{@uninvited_user.uuid}"
assert @uninvited_user.can? :manage=>"#{@uninvited_user.uuid}"
- assert @uninvited_user.groups_i_can(:read).size == 0, "inactive and uninvited user should not be able read any groups"
+ assert @uninvited_user.groups_i_can(:read).size == 1, "inactive and uninvited user can only read anonymous user group"
+ assert @uninvited_user.groups_i_can(:read).first.ends_with? 'anonymouspublic' , "inactive and uninvited user can only read anonymous user group"
assert @uninvited_user.groups_i_can(:write).size == 0, "inactive and uninvited user should not be able write to any groups"
assert @uninvited_user.groups_i_can(:manage).size == 0, "inactive and uninvited user should not be able manage any groups"
end
if args.debug:
arvados.config.settings()['ARVADOS_DEBUG'] = 'true'
- if args.groups:
+ try:
api = arvados.api('v1')
- e = operations.inodes.add_entry(GroupsDirectory(llfuse.ROOT_INODE, operations.inodes, api))
- elif args.tags:
- api = arvados.api('v1')
- e = operations.inodes.add_entry(TagsDirectory(llfuse.ROOT_INODE, operations.inodes, api))
- elif args.collection != None:
- # Set up the request handler with the collection at the root
- e = operations.inodes.add_entry(CollectionDirectory(llfuse.ROOT_INODE, operations.inodes, args.collection))
- else:
- # Set up the request handler with the 'magic directory' at the root
- operations.inodes.add_entry(MagicDirectory(llfuse.ROOT_INODE, operations.inodes))
+
+ if args.groups:
+ e = operations.inodes.add_entry(GroupsDirectory(llfuse.ROOT_INODE, operations.inodes, api))
+ elif args.tags:
+ e = operations.inodes.add_entry(TagsDirectory(llfuse.ROOT_INODE, operations.inodes, api))
+ elif args.collection != None:
+ # Set up the request handler with the collection at the root
+ e = operations.inodes.add_entry(CollectionDirectory(llfuse.ROOT_INODE, operations.inodes, args.collection))
+ else:
+ # Set up the request handler with the 'magic directory' at the root
+ operations.inodes.add_entry(MagicDirectory(llfuse.ROOT_INODE, operations.inodes))
+ except Exception as ex:
+ print("arv-mount: %s" % ex)
+ exit(1)
# FUSE options, see mount.fuse(8)
opts = [optname for optname in ['allow_other', 'debug']