send_error("Path not found", status: 404)
end
+ def render_accepted
+ send_json ({accepted: true}), status: 202
+ end
+
protected
def send_error(*args)
params
end
+ def self._create_requires_parameters
+ super.merge(
+ {
+ async: {
+ required: false,
+ type: 'boolean',
+ location: 'query',
+ default: false,
+ description: 'defer permissions update'
+ }
+ }
+ )
+ end
+
+ def self._update_requires_parameters
+ super.merge(
+ {
+ async: {
+ required: false,
+ type: 'boolean',
+ location: 'query',
+ default: false,
+ description: 'defer permissions update'
+ }
+ }
+ )
+ end
+
+ def create
+ if params[:async]
+ @object = model_class.new(resource_attrs.merge({async_permissions_update: true}))
+ @object.save!
+ render_accepted
+ else
+ super
+ end
+ end
+
+ def update
+ if params[:async]
+ attrs_to_update = resource_attrs.reject { |k, v|
+ [:kind, :etag, :href].index k
+ }.merge({async_permissions_update: true})
+ @object.update_attributes!(attrs_to_update)
+ @object.save!
+ render_accepted
+ else
+ super
+ end
+ end
+
def render_404_if_no_object
if params[:action] == 'contents'
if !params[:uuid]
class_name: 'Link',
primary_key: :uuid)
+ # If async is true at create or update, permission graph
+ # update is deferred allowing making multiple calls without the performance
+ # penalty.
+ attr_accessor :async_permissions_update
+
class PermissionDeniedError < RequestError
def http_status
403
def invalidate_permissions_cache
# Ensure a new group can be accessed by the appropriate users
# immediately after being created.
- User.invalidate_permissions_cache db_current_time.to_i
+ User.invalidate_permissions_cache db_current_time.to_i, self.async_permissions_update
end
def assign_name
true
end
- def self.invalidate_permissions_cache(timestamp=nil)
+ def self.invalidate_permissions_cache(timestamp=nil, async=false)
if Rails.configuration.async_permissions_update
timestamp = DbCurrentTime::db_current_time.to_i if timestamp.nil?
connection.execute "NOTIFY invalidate_permissions_cache, '#{timestamp}'"
else
- refresh_permission_view
+ refresh_permission_view(async)
end
end
# arrived, and deleted if their delete_at time has arrived.
trash_sweep_interval: 60
+ # Interval (seconds) between asynchronous permission view updates. Any
+ # permission-updating API called with the 'async' parameter schedules a an
+ # update on the permission view in the future, if not already scheduled.
+ async_permissions_update_interval: 60
+
# Maximum characters of (JSON-encoded) query parameters to include
# in each request log entry. When params exceed this size, they will
# be JSON-encoded, truncated to this size, and logged as
PERMISSION_VIEW = "materialized_permission_view"
-def refresh_permission_view
+def do_refresh_permission_view
ActiveRecord::Base.transaction do
ActiveRecord::Base.connection.execute("LOCK TABLE permission_refresh_lock")
ActiveRecord::Base.connection.execute("REFRESH MATERIALIZED VIEW #{PERMISSION_VIEW}")
end
end
+
+def refresh_permission_view(async=false)
+ if async and Rails.configuration.async_permissions_update_interval > 0
+ exp = Rails.configuration.async_permissions_update_interval.seconds
+ Rails.cache.fetch('AsyncRefreshPermissionView', expires_in: exp) do
+ # Schedule a new permission update and return immediately
+ Thread.new do
+ Thread.current.abort_on_exception = false
+ begin
+ sleep(exp)
+ do_refresh_permission_view
+ rescue => e
+ Rails.logger.error "Updating permission view: #{e}\n#{e.backtrace.join("\n\t")}"
+ ensure
+ ActiveRecord::Base.connection.close
+ end
+ end
+ true
+ end
+ else
+ do_refresh_permission_view
+ end
+end
new_project['name'])
end
+ test "create request with async=true returns 202 Accepted" do
+ name = "Random group #{rand(1000)}"
+ assert_equal nil, Group.find_by_name(name)
+ authorize_with :active
+ post :create, {
+ group: {
+ name: name
+ },
+ async: true
+ }
+ assert_response :accepted
+ assert_not_equal nil, Group.find_by_name(name)
+ end
+
test "unsharing a project results in hiding it from previously shared user" do
# remove sharing link for project
@controller = Arvados::V1::LinksController.new