|_. Argument |_. Type |_. Description |_. Location |_. Example |
{background:#ccffcc}.|uuid|string|The UUID of the Group to untrash.|path||
|ensure_unique_name|boolean (default false)|Rename project uniquely if untrashing it would fail with a unique name conflict.|query||
+
+h3. shared
+
+This endpoint returns the toplevel set of groups to which access is granted through a chain of one or more permission links rather than through direct ownership by the current user account. This is useful for clients which wish to browse the list of projects the user has permission to read which are not part of the "home" project tree.
+
+When called with "include=owner_uuid" this also returns (in the "included" field) the objects that own those projects (users or non-project groups).
+
+Specifically, the logic is:
+
+<pre>
+select groups that are readable by current user AND
+ (the owner_uuid is a user (but not the current user) OR
+ the owner_uuid is not readable by the current user OR
+ the owner_uuid is a group but group_class is not a project)
+</pre>
+
+In addition to the "include" parameter this endpoint also supports the same parameters as the "list method.":{{site.baseurl}}/api/methods.html#index
+
+table(table table-bordered table-condensed).
+|_. Argument |_. Type |_. Description |_. Location |_. Example |
+|include|string|If provided with the value "owner_uuid", this will return owner objects in the "included" field of the response.|query|?include=owner_uuid|
@distinct = nil
@response_resource_name = nil
@attrs = nil
+ @extra_included = nil
end
def default_url_options
:limit => @limit,
:items => @objects.as_api_response(nil, {select: @select})
}
+ if @extra_included
+ list[:included] = @extra_included.as_api_response(nil, {select: @select})
+ end
case params[:count]
when nil, '', 'exact'
if @objects.respond_to? :except
class Arvados::V1::GroupsController < ApplicationController
include TrashableController
+ skip_before_filter :find_object_by_uuid, only: :shared
+ skip_before_filter :render_404_if_no_object, only: :shared
+
def self._index_requires_parameters
(super rescue {}).
merge({
})
end
+ def shared
+ # The purpose of this endpoint is to return the toplevel set of
+ # groups which are *not* reachable through a direct ownership
+ # chain of projects starting from the current user account. In
+ # other words, groups which to which access was granted via a
+ # permission link or chain of links.
+ #
+ # This also returns (in the "included" field) the objects that own
+ # those projects (users or non-project groups).
+ #
+ # select groups that are readable by current user AND
+ # the owner_uuid is a user (but not the current user) OR
+ # the owner_uuid is not readable by the current user
+ # the owner_uuid is a group but group_class is not a project
+ #
+ # The intended use of this endpoint is to support clients which
+ # wish to browse those projects which are visible to the user but
+ # are not part of the "home" project.
+
+ load_limit_offset_order_params
+ load_filters_param
+
+ read_parent_check = if current_user.is_admin
+ ""
+ else
+ "NOT EXISTS(SELECT 1 FROM #{PERMISSION_VIEW} WHERE "+
+ "user_uuid=(:user_uuid) AND target_uuid=groups.owner_uuid AND perm_level >= 1) OR "
+ end
+
+ @objects = Group.readable_by(*@read_users).where("groups.owner_uuid IN (SELECT users.uuid FROM users WHERE users.uuid != (:user_uuid)) OR "+
+ read_parent_check+
+ "EXISTS(SELECT 1 FROM groups as gp where gp.uuid=groups.owner_uuid and gp.group_class != 'project')",
+ user_uuid: current_user.uuid)
+ apply_where_limit_order_params
+
+ owners = @objects.map(&:owner_uuid).to_a
+
+ if params["include"] == "owner_uuid"
+ @extra_included = []
+ [Group, User].each do |klass|
+ @extra_included += klass.readable_by(*@read_users).where(uuid: owners).to_a
+ end
+ end
+
+ index
+ end
+
+ def self._shared_requires_parameters
+ rp = self._index_requires_parameters
+ rp[:include] = { type: 'string', required: false }
+ rp
+ end
+
protected
def load_searchable_objects
resources :groups do
get 'contents', on: :collection
get 'contents', on: :member
+ get 'shared', on: :collection
post 'trash', on: :member
post 'untrash', on: :member
end
api_token: 2p1pou8p4ls208mcbedeewlotghppenobcyrmyhq8pyf51xd8u
expires_at: 2038-01-01 00:00:00
+user_bar_in_sharing_group:
+ uuid: zzzzz-gj3su-62hryf5fht531mz
+ api_client: untrusted
+ user: user_bar_in_sharing_group
+ api_token: 5vy55akwq85vghh80wc2cuxl4p8psay73lkpqf5c2cxvp6rmm6
+ expires_at: 2038-01-01 00:00:00
+
user1_with_load:
uuid: zzzzz-gj3su-357z32aux8dg2s1
api_client: untrusted
description: Users who can share objects with each other
group_class: role
+project_owned_by_foo:
+ uuid: zzzzz-j7d0g-lsjm0ibr0ydwpzx
+ owner_uuid: zzzzz-tpzed-81hsbo6mk8nl05c
+ created_at: 2014-02-03T17:22:54Z
+ modified_at: 2014-02-03T17:22:54Z
+ name: project_owned_by_foo
+ group_class: project
+
empty_project:
uuid: zzzzz-j7d0g-9otoxmrksam74q6
owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
assert_not_nil Group.readable_by(users(auth)).where(uuid: groups(:trashed_subproject).uuid).first
end
end
+
+ test 'get shared owned by another user' do
+ authorize_with :user_bar_in_sharing_group
+
+ act_as_system_user do
+ Link.create!(
+ tail_uuid: users(:user_bar_in_sharing_group).uuid,
+ link_class: 'permission',
+ name: 'can_read',
+ head_uuid: groups(:project_owned_by_foo).uuid)
+ end
+
+ get :shared, {:filters => [["group_class", "=", "project"]], :include => "owner_uuid"}
+
+ assert_equal 1, json_response['items'].length
+ assert_equal json_response['items'][0]["uuid"], groups(:project_owned_by_foo).uuid
+
+ assert_equal 1, json_response['included'].length
+ assert_equal json_response['included'][0]["uuid"], users(:user_foo_in_sharing_group).uuid
+ end
+
+ test 'get shared, owned by unreadable project' do
+ authorize_with :user_bar_in_sharing_group
+
+ act_as_system_user do
+ Group.find_by_uuid(groups(:project_owned_by_foo).uuid).update!(owner_uuid: groups(:aproject).uuid)
+ Link.create!(
+ tail_uuid: users(:user_bar_in_sharing_group).uuid,
+ link_class: 'permission',
+ name: 'can_read',
+ head_uuid: groups(:project_owned_by_foo).uuid)
+ end
+
+ get :shared, {:filters => [["group_class", "=", "project"]], :include => "owner_uuid"}
+
+ assert_equal 1, json_response['items'].length
+ assert_equal json_response['items'][0]["uuid"], groups(:project_owned_by_foo).uuid
+
+ assert_equal 0, json_response['included'].length
+ end
+
+ test 'get shared, owned by non-project' do
+ authorize_with :user_bar_in_sharing_group
+
+ act_as_system_user do
+ Group.find_by_uuid(groups(:project_owned_by_foo).uuid).update!(owner_uuid: groups(:group_for_sharing_tests).uuid)
+ end
+
+ get :shared, {:filters => [["group_class", "=", "project"]], :include => "owner_uuid"}
+
+ assert_equal 1, json_response['items'].length
+ assert_equal json_response['items'][0]["uuid"], groups(:project_owned_by_foo).uuid
+
+ assert_equal 1, json_response['included'].length
+ assert_equal json_response['included'][0]["uuid"], groups(:group_for_sharing_tests).uuid
+ end
+
end
with llfuse.lock_released:
if not self._current_user:
self._current_user = self.api.users().current().execute(num_retries=self.num_retries)
- return self._current_user["uuid"] in self.project_object["writable_by"]
+ return self._current_user["uuid"] in self.project_object.get("writable_by", [])
def persisted(self):
return True
if not self.stale():
return
- all_projects = arvados.util.list_all(
- self.api.groups().list, self.num_retries,
- filters=[['group_class','=','project']],
- select=["uuid", "owner_uuid"])
- objects = {}
- for ob in all_projects:
- objects[ob['uuid']] = ob
-
+ contents = {}
roots = []
root_owners = set()
- current_uuid = self.current_user['uuid']
- for ob in all_projects:
- if ob['owner_uuid'] != current_uuid and ob['owner_uuid'] not in objects:
- roots.append(ob['uuid'])
- root_owners.add(ob['owner_uuid'])
-
- lusers = arvados.util.list_all(
- self.api.users().list, self.num_retries,
- filters=[['uuid','in', list(root_owners)]])
- lgroups = arvados.util.list_all(
- self.api.groups().list, self.num_retries,
- filters=[['uuid','in', list(root_owners)+roots]])
-
- for l in lusers:
- objects[l["uuid"]] = l
- for l in lgroups:
- objects[l["uuid"]] = l
+ objects = {}
+
+ methods = self.api._rootDesc.get('resources')["groups"]['methods']
+ if 'httpMethod' in methods.get('shared', {}):
+ page = []
+ while True:
+ resp = self.api.groups().shared(filters=[['group_class', '=', 'project']]+page,
+ order="uuid",
+ limit=10000,
+ count="none",
+ include="owner_uuid").execute()
+ if not resp["items"]:
+ break
+ page = [["uuid", ">", resp["items"][len(resp["items"])-1]["uuid"]]]
+ for r in resp["items"]:
+ objects[r["uuid"]] = r
+ roots.append(r["uuid"])
+ for r in resp["included"]:
+ objects[r["uuid"]] = r
+ root_owners.add(r["uuid"])
+ else:
+ all_projects = arvados.util.list_all(
+ self.api.groups().list, self.num_retries,
+ filters=[['group_class','=','project']],
+ select=["uuid", "owner_uuid"])
+ for ob in all_projects:
+ objects[ob['uuid']] = ob
+
+ current_uuid = self.current_user['uuid']
+ for ob in all_projects:
+ if ob['owner_uuid'] != current_uuid and ob['owner_uuid'] not in objects:
+ roots.append(ob['uuid'])
+ root_owners.add(ob['owner_uuid'])
+
+ lusers = arvados.util.list_all(
+ self.api.users().list, self.num_retries,
+ filters=[['uuid','in', list(root_owners)]])
+ lgroups = arvados.util.list_all(
+ self.api.groups().list, self.num_retries,
+ filters=[['uuid','in', list(root_owners)+roots]])
+
+ for l in lusers:
+ objects[l["uuid"]] = l
+ for l in lgroups:
+ objects[l["uuid"]] = l
- contents = {}
for r in root_owners:
if r in objects:
obr = objects[r]