return conn.chooseBackend(options.UUID).CollectionUntrash(ctx, options)
}
+func (conn *Conn) ComputedPermissionList(ctx context.Context, options arvados.ListOptions) (arvados.ComputedPermissionList, error) {
+ return conn.local.ComputedPermissionList(ctx, options)
+}
+
func (conn *Conn) ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
return conn.generated_ContainerList(ctx, options)
}
return rtr.backend.CollectionUntrash(ctx, *opts.(*arvados.UntrashOptions))
},
},
+ {
+ arvados.EndpointComputedPermissionList,
+ func() interface{} { return &arvados.ListOptions{Limit: -1} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.backend.ComputedPermissionList(ctx, *opts.(*arvados.ListOptions))
+ },
+ },
{
arvados.EndpointContainerCreate,
func() interface{} { return &arvados.CreateOptions{} },
return resp, err
}
+func (conn *Conn) ComputedPermissionList(ctx context.Context, options arvados.ListOptions) (arvados.ComputedPermissionList, error) {
+ ep := arvados.EndpointComputedPermissionList
+ var resp arvados.ComputedPermissionList
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+
func (conn *Conn) ContainerCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Container, error) {
ep := arvados.EndpointContainerCreate
var resp arvados.Container
EndpointCollectionDelete = APIEndpoint{"DELETE", "arvados/v1/collections/{uuid}", ""}
EndpointCollectionTrash = APIEndpoint{"POST", "arvados/v1/collections/{uuid}/trash", ""}
EndpointCollectionUntrash = APIEndpoint{"POST", "arvados/v1/collections/{uuid}/untrash", ""}
+ EndpointComputedPermissionList = APIEndpoint{"GET", "arvados/v1/computed_permissions", ""}
EndpointContainerCreate = APIEndpoint{"POST", "arvados/v1/containers", "container"}
EndpointContainerUpdate = APIEndpoint{"PATCH", "arvados/v1/containers/{uuid}", "container"}
EndpointContainerPriorityUpdate = APIEndpoint{"POST", "arvados/v1/containers/{uuid}/update_priority", "container"}
CollectionDelete(ctx context.Context, options DeleteOptions) (Collection, error)
CollectionTrash(ctx context.Context, options DeleteOptions) (Collection, error)
CollectionUntrash(ctx context.Context, options UntrashOptions) (Collection, error)
+ ComputedPermissionList(ctx context.Context, options ListOptions) (ComputedPermissionList, error)
ContainerCreate(ctx context.Context, options CreateOptions) (Container, error)
ContainerUpdate(ctx context.Context, options UpdateOptions) (Container, error)
ContainerPriorityUpdate(ctx context.Context, options UpdateOptions) (Container, error)
Offset int `json:"offset"`
Limit int `json:"limit"`
}
+
+type ComputedPermission struct {
+ UserUUID string `json:"user_uuid"`
+ TargetUUID string `json:"target_uuid"`
+ PermLevel string `json:"perm_level"`
+}
+
+type ComputedPermissionList struct {
+ Items []ComputedPermission `json:"items"`
+ ItemsAvailable int `json:"items_available"`
+ Limit int `json:"limit"`
+}
as.appendCall(ctx, as.CollectionUntrash, options)
return arvados.Collection{}, as.Error
}
+func (as *APIStub) ComputedPermissionList(ctx context.Context, options arvados.ListOptions) (arvados.ComputedPermissionList, error) {
+ as.appendCall(ctx, as.ComputedPermissionList, options)
+ return arvados.ComputedPermissionList{}, as.Error
+}
func (as *APIStub) ContainerCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Container, error) {
as.appendCall(ctx, as.ContainerCreate, options)
return arvados.Container{}, as.Error
}
}
},
+ "computed_permissions": {
+ "methods": {
+ "list": {
+ "id": "arvados.computed_permissions.list",
+ "path": "computed_permissions",
+ "httpMethod": "GET",
+ "description": "List ComputedPermissions.\n\n The <code>list</code> method returns a\n <a href=\"/api/resources.html\">resource list</a> of\n matching ComputedPermissions. For example:\n\n <pre>\n {\n \"kind\":\"arvados#computedPermissionList\",\n \"etag\":\"\",\n \"self_link\":\"\",\n \"next_page_token\":\"\",\n \"next_link\":\"\",\n \"items\":[\n ...\n ],\n \"items_available\":745,\n \"_profile\":{\n \"request_time\":0.157236317\n }\n </pre>",
+ "parameters": {
+ "filters": {
+ "type": "array",
+ "required": false,
+ "description": "",
+ "location": "query"
+ },
+ "where": {
+ "type": "object",
+ "required": false,
+ "description": "",
+ "location": "query"
+ },
+ "order": {
+ "type": "array",
+ "required": false,
+ "description": "",
+ "location": "query"
+ },
+ "select": {
+ "type": "array",
+ "description": "Attributes of each object to return in the response.",
+ "required": false,
+ "location": "query"
+ },
+ "distinct": {
+ "type": "boolean",
+ "required": false,
+ "default": "false",
+ "description": "",
+ "location": "query"
+ },
+ "limit": {
+ "type": "integer",
+ "required": false,
+ "default": "100",
+ "description": "",
+ "location": "query"
+ },
+ "count": {
+ "type": "string",
+ "required": false,
+ "default": "exact",
+ "description": "",
+ "location": "query"
+ }
+ },
+ "response": {
+ "$ref": "ComputedPermissionList"
+ },
+ "scopes": [
+ "https://api.arvados.org/auth/arvados",
+ "https://api.arvados.org/auth/arvados.readonly"
+ ]
+ }
+ }
+ },
"containers": {
"methods": {
"get": {
}
}
},
+ "ComputedPermissionList": {
+ "id": "ComputedPermissionList",
+ "description": "ComputedPermission list",
+ "type": "object",
+ "properties": {
+ "kind": {
+ "type": "string",
+ "description": "Object type. Always arvados#computedPermissionList.",
+ "default": "arvados#computedPermissionList"
+ },
+ "etag": {
+ "type": "string",
+ "description": "List version."
+ },
+ "items": {
+ "type": "array",
+ "description": "The list of ComputedPermissions.",
+ "items": {
+ "$ref": "ComputedPermission"
+ }
+ },
+ "next_link": {
+ "type": "string",
+ "description": "A link to the next page of ComputedPermissions."
+ },
+ "next_page_token": {
+ "type": "string",
+ "description": "The page token for the next page of ComputedPermissions."
+ },
+ "selfLink": {
+ "type": "string",
+ "description": "A link back to this list."
+ }
+ }
+ },
+ "ComputedPermission": {
+ "id": "ComputedPermission",
+ "description": "ComputedPermission",
+ "type": "object",
+ "properties": {
+ "user_uuid": {
+ "type": "string"
+ },
+ "target_uuid": {
+ "type": "string"
+ },
+ "perm_level": {
+ "type": "integer"
+ }
+ }
+ },
"ContainerList": {
"id": "ContainerList",
"description": "Container list",
--- /dev/null
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+import arvados
+from . import run_test_server
+
+class ComputedPermissionTest(run_test_server.TestCaseWithServers):
+ def test_computed_permission(self):
+ run_test_server.authorize_with('admin')
+ api_client = arvados.api('v1')
+ active_user_uuid = run_test_server.fixture('users')['active']['uuid']
+ resp = api_client.computed_permissions().list(
+ filters=[['user_uuid', '=', active_user_uuid]],
+ ).execute()
+ assert len(resp['items']) > 0
+ for item in resp['items']:
+ assert item['user_uuid'] == active_user_uuid
--- /dev/null
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+class Arvados::V1::ComputedPermissionsController < ApplicationController
+ before_action :admin_required
+end
end
end
+ # The computed_permissions controller does not offer all of the
+ # usual methods and attributes. Modify discovery doc accordingly.
+ discovery[:resources]['computed_permissions'][:methods].select! do |method|
+ method == :list
+ end
+ discovery[:resources]['computed_permissions'][:methods][:list][:parameters].select! do |param|
+ ![:cluster_id, :bypass_federation, :offset].include?(param)
+ end
+ discovery[:schemas]['ComputedPermission'].delete(:uuidPrefix)
+ discovery[:schemas]['ComputedPermission'][:properties].select! do |prop|
+ ![:uuid, :etag].include?(prop)
+ end
+
# The 'replace_files' option is implemented in lib/controller,
# not Rails -- we just need to add it here so discovery-aware
# clients know how to validate it.
end.map(&:name)
end
- def self.attribute_column attr
- self.columns.select { |col| col.name == attr.to_s }.first
- end
-
def self.attributes_required_columns
# This method returns a hash. Each key is the name of an API attribute,
# and it's mapped to a list of database columns that must be fetched
"to_tsvector('english', substr(#{parts.join(" || ' ' || ")}, 0, 8000))"
end
- def self.apply_filters query, filters
- ft = record_filters filters, self
- if not ft[:cond_out].any?
- return query
- end
- ft[:joins].each do |t|
- query = query.joins(t)
- end
- query.where('(' + ft[:cond_out].join(') AND (') + ')',
- *ft[:param_out])
- end
-
@_add_uuid_to_name = false
def add_uuid_to_make_unique_name
@_add_uuid_to_name = true
--- /dev/null
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+require 'record_filters'
+
+class ComputedPermission < ApplicationRecord
+ self.table_name = 'materialized_permissions'
+ include CurrentApiClient
+ include CommonApiTemplate
+ extend RecordFilters
+
+ PERM_LEVEL_S = ['none', 'can_read', 'can_write', 'can_manage']
+
+ api_accessible :user do |t|
+ t.add :user_uuid
+ t.add :target_uuid
+ t.add :perm_level_s, as: :perm_level
+ end
+
+ protected
+
+ def perm_level_s
+ PERM_LEVEL_S[perm_level]
+ end
+
+ def self.default_orders
+ ["#{table_name}.user_uuid", "#{table_name}.target_uuid"]
+ end
+
+ def self.readable_by(*args)
+ self
+ end
+
+ def self.searchable_columns(operator)
+ if !operator.match(/[<=>]/) && !operator.in?(['in', 'not in'])
+ []
+ else
+ ['user_uuid', 'target_uuid']
+ end
+ end
+
+ def self.limit_index_columns_read
+ []
+ end
+
+ def self.selectable_attributes
+ %w(user_uuid target_uuid perm_level)
+ end
+
+ def self.columns_for_attributes(select_attributes)
+ select_attributes
+ end
+
+ def self.serialized_attributes
+ {}
+ end
+end
def before_ownership_change
if owner_uuid_changed? and !self.owner_uuid_was.nil?
- MaterializedPermission.where(user_uuid: owner_uuid_was, target_uuid: uuid).delete_all
+ ComputedPermission.where(user_uuid: owner_uuid_was, target_uuid: uuid).delete_all
update_permissions self.owner_uuid_was, self.uuid, REVOKE_PERM
end
end
end
def clear_permissions_trash_frozen
- MaterializedPermission.where(target_uuid: uuid).delete_all
+ ComputedPermission.where(target_uuid: uuid).delete_all
ActiveRecord::Base.connection.exec_delete(
"delete from trashed_groups where group_uuid=$1",
"Group.clear_permissions_trash_frozen",
+++ /dev/null
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class MaterializedPermission < ApplicationRecord
-end
def before_ownership_change
if owner_uuid_changed? and !self.owner_uuid_was.nil?
- MaterializedPermission.where(user_uuid: owner_uuid_was, target_uuid: uuid).delete_all
+ ComputedPermission.where(user_uuid: owner_uuid_was, target_uuid: uuid).delete_all
update_permissions self.owner_uuid_was, self.uuid, REVOKE_PERM
end
end
end
def clear_permissions
- MaterializedPermission.where("user_uuid = ? and target_uuid != ?", uuid, uuid).delete_all
+ ComputedPermission.where("user_uuid = ? and target_uuid != ?", uuid, uuid).delete_all
end
def forget_cached_group_perms
end
def remove_self_from_permissions
- MaterializedPermission.where("target_uuid = ?", uuid).delete_all
+ ComputedPermission.where("target_uuid = ?", uuid).delete_all
check_permissions_against_full_refresh
end
end
resources :links
resources :logs
- resources :workflows
resources :user_agreements do
get 'signatures', on: :collection
post 'sign', on: :collection
get 'logins', on: :member
get 'get_all_logins', on: :collection
end
+ resources :workflows
+ get '/computed_permissions', to: 'computed_permissions#index'
get '/permissions/:uuid', to: 'links#get_permissions'
end
end
'jobs',
'job_tasks',
'keep_disks',
+ 'materialized_permissions',
'nodes',
'pipeline_instances',
'pipeline_templates',
{:cond_out => conds_out, :param_out => param_out, :joins => joins}
end
+ def apply_filters query, filters
+ ft = record_filters filters, self
+ if not ft[:cond_out].any?
+ return query
+ end
+ ft[:joins].each do |t|
+ query = query.joins(t)
+ end
+ query.where('(' + ft[:cond_out].join(') AND (') + ')',
+ *ft[:param_out])
+ end
+
+ def attribute_column attr
+ self.columns.select { |col| col.name == attr.to_s }.first
+ end
end
--- /dev/null
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+require 'test_helper'
+
+class Arvados::V1::ComputedPermissionsControllerTest < ActionController::TestCase
+ test "require auth" do
+ get :index, params: {}
+ assert_response 401
+ end
+
+ test "require admin" do
+ authorize_with :active
+ get :index, params: {}
+ assert_response 403
+ end
+
+ test "index with no options" do
+ authorize_with :admin
+ get :index, params: {}
+ assert_response :success
+ assert_operator 0, :<, json_response['items'].length
+
+ last_user = ''
+ last_target = ''
+ json_response['items'].each do |item|
+ assert_not_empty item['user_uuid']
+ assert_not_empty item['target_uuid']
+ assert_not_empty item['perm_level']
+ # check default ordering
+ assert_operator last_user, :<=, item['user_uuid']
+ if last_user == item['user_uuid']
+ assert_operator last_target, :<=, item['target_uuid']
+ end
+ last_user = item['user_uuid']
+ last_target = item['target_uuid']
+ end
+ end
+
+ test "index with limit" do
+ authorize_with :admin
+ get :index, params: {limit: 10}
+ assert_response :success
+ assert_equal 10, json_response['items'].length
+ end
+
+ test "index with filter on user_uuid" do
+ user_uuid = users(:active).uuid
+ authorize_with :admin
+ get :index, params: {filters: [['user_uuid', '=', user_uuid]]}
+ assert_response :success
+ assert_not_equal 0, json_response['items'].length
+ json_response['items'].each do |item|
+ assert_equal user_uuid, item['user_uuid']
+ end
+ end
+
+ test "index with filter on user_uuid and target_uuid" do
+ user_uuid = users(:active).uuid
+ target_uuid = groups(:aproject).uuid
+ authorize_with :admin
+ get :index, params: {filters: [
+ ['user_uuid', '=', user_uuid],
+ ['target_uuid', '=', target_uuid],
+ ]}
+ assert_response :success
+ assert_equal([{"user_uuid" => user_uuid,
+ "target_uuid" => target_uuid,
+ "perm_level" => "can_manage",
+ }],
+ json_response['items'])
+ end
+
+ test "index with disallowed filters" do
+ authorize_with :admin
+ get :index, params: {filters: [['perm_level', '=', 'can_manage']]}
+ assert_response 422
+ end
+
+ %w(user_uuid target_uuid perm_level).each do |attr|
+ test "select only #{attr}" do
+ authorize_with :admin
+ get :index, params: {select: [attr], limit: 1}
+ assert_response :success
+ assert_operator 0, :<, json_response['items'][0][attr].length
+ assert_equal([{attr => json_response['items'][0][attr]}], json_response['items'])
+ end
+ end
+end
--- /dev/null
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+require 'test_helper'
+
+class ComputedPermissionsTest < ActionDispatch::IntegrationTest
+ include DbCurrentTime
+ fixtures :users, :groups, :api_client_authorizations, :collections
+
+ test "non-admin forbidden" do
+ get "/arvados/v1/computed_permissions",
+ params: {:format => :json},
+ headers: auth(:active)
+ assert_response 403
+ end
+
+ test "admin get permission for specified user" do
+ get "/arvados/v1/computed_permissions",
+ params: {
+ :format => :json,
+ :filters => [['user_uuid', '=', users(:active).uuid]].to_json,
+ },
+ headers: auth(:admin)
+ assert_response :success
+ assert_equal users(:active).uuid, json_response['items'][0]['user_uuid']
+ end
+end
if expected_json != actual_json
File.open(out_path, "w") { |f| f.write(actual_json) }
end
- assert_equal(expected_json, actual_json, [
- "#{src_path} did not match the live discovery document",
- "Current live version saved to #{out_path}",
- "Commit that to #{src_path} to regenerate documentation",
- ].join(". "))
+ assert_equal(expected_json, actual_json,
+ "Live discovery document did not match the expected version (#{src_path}). " +
+ "If the live version is correct, copy it to the git working directory by running:\n" +
+ "cp #{out_path} #{src_path}\n")
end
end