From 74323ae3de455071de4fce0c2e2ee79a5650a040 Mon Sep 17 00:00:00 2001 From: Tom Clegg Date: Mon, 6 Jun 2022 11:27:52 -0400 Subject: [PATCH] 19146: Add can_write and can_manage response fields. Arvados-DCO-1.1-Signed-off-by: Tom Clegg --- doc/api/methods/groups.html.textile.liquid | 4 +- lib/controller/localdb/group_test.go | 111 +++++++++++++++++++-- sdk/go/arvados/group.go | 2 + sdk/go/arvados/user.go | 2 + services/api/app/models/arvados_model.rb | 16 +++ services/api/app/models/group.rb | 2 + services/api/app/models/user.rb | 2 + 7 files changed, 129 insertions(+), 10 deletions(-) diff --git a/doc/api/methods/groups.html.textile.liquid b/doc/api/methods/groups.html.textile.liquid index 2a762d9248..db0aac3c7a 100644 --- a/doc/api/methods/groups.html.textile.liquid +++ b/doc/api/methods/groups.html.textile.liquid @@ -30,7 +30,9 @@ table(table table-bordered table-condensed). @"role"@| |description|text||| |properties|hash|User-defined metadata, may be used in queries using "subproperty filters":{{site.baseurl}}/api/methods.html#subpropertyfilters || -|writable_by|array|List of UUID strings identifying Users and other Groups that have write permission for this Group. Only users who are allowed to administer the Group will receive a full list. Other users will receive a partial list that includes the Group's owner_uuid and (if applicable) their own user UUID.|| +|writable_by|array|(Deprecated) List of UUID strings identifying Users and other Groups that have write permission for this Group. Users who are allowed to administer the Group will receive a list of user/group UUIDs that have permission via explicit permission links; permissions via parent/ancestor groups are not taken into account. Other users will receive a partial list including only the Group's owner_uuid and (if applicable) their own user UUID.|| +|can_write|boolean|True if the current user has write permission on this group.|| +|can_manage|boolean|True if the current user has manage permission on this group.|| |trash_at|datetime|If @trash_at@ is non-null and in the past, this group and all objects directly or indirectly owned by the group will be hidden from API calls. May be untrashed.|| |delete_at|datetime|If @delete_at@ is non-null and in the past, the group and all objects directly or indirectly owned by the group may be permanently deleted.|| |is_trashed|datetime|True if @trash_at@ is in the past, false if not.|| diff --git a/lib/controller/localdb/group_test.go b/lib/controller/localdb/group_test.go index 2d55def9f6..1fde64d119 100644 --- a/lib/controller/localdb/group_test.go +++ b/lib/controller/localdb/group_test.go @@ -24,14 +24,7 @@ type GroupSuite struct { railsSpy *arvadostest.Proxy } -func (s *GroupSuite) TearDownSuite(c *check.C) { - // Undo any changes/additions to the user database so they - // don't affect subsequent tests. - arvadostest.ResetEnv() - c.Check(arvados.NewClientFromEnv().RequestAndDecode(nil, "POST", "database/reset", nil, nil), check.IsNil) -} - -func (s *GroupSuite) SetUpTest(c *check.C) { +func (s *GroupSuite) SetUpSuite(c *check.C) { cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).Load() c.Assert(err, check.IsNil) s.cluster, err = cfg.GetCluster("") @@ -41,8 +34,12 @@ func (s *GroupSuite) SetUpTest(c *check.C) { *s.localdb.railsProxy = *rpc.NewConn(s.cluster.ClusterID, s.railsSpy.URL, true, rpc.PassthroughTokenProvider) } -func (s *GroupSuite) TearDownTest(c *check.C) { +func (s *GroupSuite) TearDownSuite(c *check.C) { s.railsSpy.Close() + // Undo any changes/additions to the user database so they + // don't affect subsequent tests. + arvadostest.ResetEnv() + c.Check(arvados.NewClientFromEnv().RequestAndDecode(nil, "POST", "database/reset", nil, nil), check.IsNil) } func (s *GroupSuite) setUpVocabulary(c *check.C, testVocabulary string) { @@ -136,3 +133,99 @@ func (s *GroupSuite) TestGroupUpdateWithProperties(c *check.C) { } } } + +func (s *GroupSuite) TestCanWriteCanManageResponses(c *check.C) { + ctxUser1 := auth.NewContext(context.Background(), &auth.Credentials{Tokens: []string{arvadostest.ActiveTokenV2}}) + ctxUser2 := auth.NewContext(context.Background(), &auth.Credentials{Tokens: []string{arvadostest.SpectatorToken}}) + ctxAdmin := auth.NewContext(context.Background(), &auth.Credentials{Tokens: []string{arvadostest.AdminToken}}) + project, err := s.localdb.GroupCreate(ctxUser1, arvados.CreateOptions{ + Attrs: map[string]interface{}{ + "group_class": "project", + }, + }) + c.Assert(err, check.IsNil) + c.Check(project.CanWrite, check.Equals, true) + c.Check(project.CanManage, check.Equals, true) + + subproject, err := s.localdb.GroupCreate(ctxUser1, arvados.CreateOptions{ + Attrs: map[string]interface{}{ + "owner_uuid": project.UUID, + "group_class": "project", + }, + }) + c.Assert(err, check.IsNil) + c.Check(subproject.CanWrite, check.Equals, true) + c.Check(subproject.CanManage, check.Equals, true) + + // Give 2nd user permission to read + permlink, err := s.localdb.LinkCreate(ctxAdmin, arvados.CreateOptions{ + Attrs: map[string]interface{}{ + "link_class": "permission", + "name": "can_read", + "tail_uuid": arvadostest.SpectatorUserUUID, + "head_uuid": project.UUID, + }, + }) + c.Assert(err, check.IsNil) + + // As 2nd user: can read, cannot manage, cannot write + project2, err := s.localdb.GroupGet(ctxUser2, arvados.GetOptions{UUID: project.UUID}) + c.Assert(err, check.IsNil) + c.Check(project2.CanWrite, check.Equals, false) + c.Check(project2.CanManage, check.Equals, false) + + _, err = s.localdb.LinkUpdate(ctxAdmin, arvados.UpdateOptions{ + UUID: permlink.UUID, + Attrs: map[string]interface{}{ + "name": "can_write", + }, + }) + c.Assert(err, check.IsNil) + + // As 2nd user: cannot manage, can write + project2, err = s.localdb.GroupGet(ctxUser2, arvados.GetOptions{UUID: project.UUID}) + c.Assert(err, check.IsNil) + c.Check(project2.CanWrite, check.Equals, true) + c.Check(project2.CanManage, check.Equals, false) + + // As owner: after freezing, can manage (owner), cannot write (frozen) + project, err = s.localdb.GroupUpdate(ctxUser1, arvados.UpdateOptions{ + UUID: project.UUID, + Attrs: map[string]interface{}{ + "frozen_by_uuid": arvadostest.ActiveUserUUID, + }}) + c.Assert(err, check.IsNil) + c.Check(project.CanWrite, check.Equals, false) + c.Check(project.CanManage, check.Equals, true) + + // As admin: can manage (admin), cannot write (frozen) + project, err = s.localdb.GroupGet(ctxAdmin, arvados.GetOptions{UUID: project.UUID}) + c.Assert(err, check.IsNil) + c.Check(project.CanWrite, check.Equals, false) + c.Check(project.CanManage, check.Equals, true) + + // As 2nd user: cannot manage (perm), cannot write (frozen) + project2, err = s.localdb.GroupGet(ctxUser2, arvados.GetOptions{UUID: project.UUID}) + c.Assert(err, check.IsNil) + c.Check(project2.CanWrite, check.Equals, false) + c.Check(project2.CanManage, check.Equals, false) + + // After upgrading perm to "manage", as 2nd user: can manage (perm), cannot write (frozen) + _, err = s.localdb.LinkUpdate(ctxAdmin, arvados.UpdateOptions{ + UUID: permlink.UUID, + Attrs: map[string]interface{}{ + "name": "can_manage", + }, + }) + c.Assert(err, check.IsNil) + project2, err = s.localdb.GroupGet(ctxUser2, arvados.GetOptions{UUID: project.UUID}) + c.Assert(err, check.IsNil) + c.Check(project2.CanWrite, check.Equals, false) + c.Check(project2.CanManage, check.Equals, true) + + // 2nd user can also manage (but not write) the subject inside the frozen project + subproject2, err := s.localdb.GroupGet(ctxUser2, arvados.GetOptions{UUID: subproject.UUID}) + c.Assert(err, check.IsNil) + c.Check(subproject2.CanWrite, check.Equals, false) + c.Check(subproject2.CanManage, check.Equals, true) +} diff --git a/sdk/go/arvados/group.go b/sdk/go/arvados/group.go index ad7ac1ee2b..0782bd43d1 100644 --- a/sdk/go/arvados/group.go +++ b/sdk/go/arvados/group.go @@ -27,6 +27,8 @@ type Group struct { WritableBy []string `json:"writable_by,omitempty"` Description string `json:"description"` FrozenByUUID string `json:"frozen_by_uuid"` + CanWrite bool `json:"can_write"` + CanManage bool `json:"can_manage"` } // GroupList is an arvados#groupList resource. diff --git a/sdk/go/arvados/user.go b/sdk/go/arvados/user.go index 68960144a8..2fb061e7fb 100644 --- a/sdk/go/arvados/user.go +++ b/sdk/go/arvados/user.go @@ -26,6 +26,8 @@ type User struct { ModifiedByClientUUID string `json:"modified_by_client_uuid"` Prefs map[string]interface{} `json:"prefs"` WritableBy []string `json:"writable_by,omitempty"` + CanWrite bool `json:"can_write"` + CanManage bool `json:"can_manage"` } // UserList is an arvados#userList resource. diff --git a/services/api/app/models/arvados_model.rb b/services/api/app/models/arvados_model.rb index 07a31d81a8..e7ffe740b1 100644 --- a/services/api/app/models/arvados_model.rb +++ b/services/api/app/models/arvados_model.rb @@ -273,6 +273,22 @@ class ArvadosModel < ApplicationRecord end.compact.uniq end + def can_write + if respond_to?(:frozen_by_uuid) && frozen_by_uuid + return false + else + return owner_uuid == current_user.uuid || + current_user.is_admin || + current_user.can?(write: uuid) + end + end + + def can_manage + return owner_uuid == current_user.uuid || + current_user.is_admin || + current_user.can?(manage: uuid) + end + # Return a query with read permissions restricted to the union of the # permissions of the members of users_list, i.e. if something is readable by # any user in users_list, it will be readable in the query returned by this diff --git a/services/api/app/models/group.rb b/services/api/app/models/group.rb index b1b2e942c6..e18ee5ef38 100644 --- a/services/api/app/models/group.rb +++ b/services/api/app/models/group.rb @@ -44,6 +44,8 @@ class Group < ArvadosModel t.add :is_trashed t.add :properties t.add :frozen_by_uuid + t.add :can_write + t.add :can_manage end def ensure_filesystem_compatible_name diff --git a/services/api/app/models/user.rb b/services/api/app/models/user.rb index bbb2378f5c..52b96f9c51 100644 --- a/services/api/app/models/user.rb +++ b/services/api/app/models/user.rb @@ -72,6 +72,8 @@ class User < ArvadosModel t.add :is_invited t.add :prefs t.add :writable_by + t.add :can_write + t.add :can_manage end ALL_PERMISSIONS = {read: true, write: true, manage: true} -- 2.30.2