resource_attrs.delete :head_kind
resource_attrs.delete :tail_kind
+
+ if resource_attrs[:link_class] == 'permission' && Link::PermLevel[resource_attrs[:name]]
+ existing = Link.
+ lock. # select ... for update
+ where(link_class: 'permission',
+ tail_uuid: resource_attrs[:tail_uuid],
+ head_uuid: resource_attrs[:head_uuid],
+ name: Link::PermLevel.keys).first
+ if existing
+ @object = existing
+ if Link::PermLevel[resource_attrs[:name]] > Link::PermLevel[existing.name]
+ # upgrade existing permission link to the requested level.
+ return update
+ else
+ # no-op: existing permission is already greater or equal to
+ # the newly requested permission.
+ return show
+ end
+ end
+ elsif resource_attrs[:link_class] == 'permission' &&
+ resource_attrs[:name] == 'can_login' &&
+ resource_attrs[:properties].respond_to?(:has_key?) &&
+ resource_attrs[:properties].has_key?(:username)
+ existing = Link.
+ lock. # select ... for update
+ where(link_class: 'permission',
+ tail_uuid: resource_attrs[:tail_uuid],
+ head_uuid: resource_attrs[:head_uuid]).
+ where('properties @> ?', SafeJSON.dump({'username' => resource_attrs[:properties][:username]})).
+ first
+ if existing
+ @object = existing
+ return show
+ end
+ end
+
super
end
protected
- def find_object_by_uuid
+ def find_object_by_uuid(with_lock: false)
if params[:id] && params[:id].match(/\D/)
params[:uuid] = params.delete :id
end
.where(uuid: params[:uuid])
.first
elsif !current_user
- super
+ super(with_lock: with_lock)
else
# The usual permission-filtering index query is unnecessarily
# inefficient, and doesn't match all permission links that
# by UUID, then check whether (a) its tail_uuid is the current
# user or (b) its head_uuid is an object the current_user
# can_manage.
- @object = Link.unscoped.where(uuid: params[:uuid]).first
- if @object && @object.link_class != 'permission'
- # Throw this out and re-fetch using generic permission query
- @object = nil
- super
- elsif @object &&
- current_user.uuid != @object.tail_uuid &&
- !current_user.can?(manage: @object.head_uuid)
+ model = Link
+ if with_lock && Rails.configuration.API.LockBeforeUpdate
+ model = model.lock
+ end
+ link = model.unscoped.where(uuid: params[:uuid]).first
+ if link && link.link_class != 'permission'
+ # Not a permission link. Re-fetch using generic
+ # permission-filtering query.
+ super(with_lock: with_lock)
+ elsif link && (current_user.uuid == link.tail_uuid ||
+ current_user.can?(manage: link.head_uuid))
+ # Permission granted.
+ @object = link
+ else
+ # Permission denied, i.e., link is invisible => 404.
@object = nil
end
end
if k[1] == '=' && current_user.can?(manage: k[2])
@objects = Link.unscoped
elsif k[1] == 'in'
+ # Modify the filter operand element (k[2]) in place,
+ # removing any non-permitted UUIDs.
k[2].select! do |head_uuid|
current_user.can?(manage: head_uuid)
end