19146: Add can_write/can_manage to users#list, fix select=can_*.
[arvados.git] / lib / controller / localdb / group_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package localdb
6
7 import (
8         "context"
9
10         "git.arvados.org/arvados.git/lib/config"
11         "git.arvados.org/arvados.git/lib/controller/rpc"
12         "git.arvados.org/arvados.git/sdk/go/arvados"
13         "git.arvados.org/arvados.git/sdk/go/arvadostest"
14         "git.arvados.org/arvados.git/sdk/go/auth"
15         "git.arvados.org/arvados.git/sdk/go/ctxlog"
16         check "gopkg.in/check.v1"
17 )
18
19 var _ = check.Suite(&GroupSuite{})
20
21 type GroupSuite struct {
22         cluster  *arvados.Cluster
23         localdb  *Conn
24         railsSpy *arvadostest.Proxy
25 }
26
27 func (s *GroupSuite) SetUpSuite(c *check.C) {
28         cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).Load()
29         c.Assert(err, check.IsNil)
30         s.cluster, err = cfg.GetCluster("")
31         c.Assert(err, check.IsNil)
32         s.localdb = NewConn(s.cluster)
33         s.railsSpy = arvadostest.NewProxy(c, s.cluster.Services.RailsAPI)
34         *s.localdb.railsProxy = *rpc.NewConn(s.cluster.ClusterID, s.railsSpy.URL, true, rpc.PassthroughTokenProvider)
35 }
36
37 func (s *GroupSuite) TearDownSuite(c *check.C) {
38         s.railsSpy.Close()
39         // Undo any changes/additions to the user database so they
40         // don't affect subsequent tests.
41         arvadostest.ResetEnv()
42         c.Check(arvados.NewClientFromEnv().RequestAndDecode(nil, "POST", "database/reset", nil, nil), check.IsNil)
43 }
44
45 func (s *GroupSuite) setUpVocabulary(c *check.C, testVocabulary string) {
46         if testVocabulary == "" {
47                 testVocabulary = `{
48                         "strict_tags": false,
49                         "tags": {
50                                 "IDTAGIMPORTANCES": {
51                                         "strict": true,
52                                         "labels": [{"label": "Importance"}, {"label": "Priority"}],
53                                         "values": {
54                                                 "IDVALIMPORTANCES1": { "labels": [{"label": "Critical"}, {"label": "Urgent"}, {"label": "High"}] },
55                                                 "IDVALIMPORTANCES2": { "labels": [{"label": "Normal"}, {"label": "Moderate"}] },
56                                                 "IDVALIMPORTANCES3": { "labels": [{"label": "Low"}] }
57                                         }
58                                 }
59                         }
60                 }`
61         }
62         voc, err := arvados.NewVocabulary([]byte(testVocabulary), []string{})
63         c.Assert(err, check.IsNil)
64         s.localdb.vocabularyCache = voc
65         s.cluster.API.VocabularyPath = "foo"
66 }
67
68 func (s *GroupSuite) TestGroupCreateWithProperties(c *check.C) {
69         s.setUpVocabulary(c, "")
70         ctx := auth.NewContext(context.Background(), &auth.Credentials{Tokens: []string{arvadostest.ActiveTokenV2}})
71
72         tests := []struct {
73                 name    string
74                 props   map[string]interface{}
75                 success bool
76         }{
77                 {"Invalid prop key", map[string]interface{}{"Priority": "IDVALIMPORTANCES1"}, false},
78                 {"Invalid prop value", map[string]interface{}{"IDTAGIMPORTANCES": "high"}, false},
79                 {"Valid prop key & value", map[string]interface{}{"IDTAGIMPORTANCES": "IDVALIMPORTANCES1"}, true},
80                 {"Empty properties", map[string]interface{}{}, true},
81         }
82         for _, tt := range tests {
83                 c.Log(c.TestName()+" ", tt.name)
84
85                 grp, err := s.localdb.GroupCreate(ctx, arvados.CreateOptions{
86                         Select: []string{"uuid", "properties"},
87                         Attrs: map[string]interface{}{
88                                 "group_class": "project",
89                                 "properties":  tt.props,
90                         }})
91                 if tt.success {
92                         c.Assert(err, check.IsNil)
93                         c.Assert(grp.Properties, check.DeepEquals, tt.props)
94                 } else {
95                         c.Assert(err, check.NotNil)
96                 }
97         }
98 }
99
100 func (s *GroupSuite) TestGroupUpdateWithProperties(c *check.C) {
101         s.setUpVocabulary(c, "")
102         ctx := auth.NewContext(context.Background(), &auth.Credentials{Tokens: []string{arvadostest.ActiveTokenV2}})
103
104         tests := []struct {
105                 name    string
106                 props   map[string]interface{}
107                 success bool
108         }{
109                 {"Invalid prop key", map[string]interface{}{"Priority": "IDVALIMPORTANCES1"}, false},
110                 {"Invalid prop value", map[string]interface{}{"IDTAGIMPORTANCES": "high"}, false},
111                 {"Valid prop key & value", map[string]interface{}{"IDTAGIMPORTANCES": "IDVALIMPORTANCES1"}, true},
112                 {"Empty properties", map[string]interface{}{}, true},
113         }
114         for _, tt := range tests {
115                 c.Log(c.TestName()+" ", tt.name)
116                 grp, err := s.localdb.GroupCreate(ctx, arvados.CreateOptions{
117                         Attrs: map[string]interface{}{
118                                 "group_class": "project",
119                         },
120                 })
121                 c.Assert(err, check.IsNil)
122                 grp, err = s.localdb.GroupUpdate(ctx, arvados.UpdateOptions{
123                         UUID:   grp.UUID,
124                         Select: []string{"uuid", "properties"},
125                         Attrs: map[string]interface{}{
126                                 "properties": tt.props,
127                         }})
128                 if tt.success {
129                         c.Assert(err, check.IsNil)
130                         c.Assert(grp.Properties, check.DeepEquals, tt.props)
131                 } else {
132                         c.Assert(err, check.NotNil)
133                 }
134         }
135 }
136
137 func (s *GroupSuite) TestCanWriteCanManageResponses(c *check.C) {
138         ctxUser1 := auth.NewContext(context.Background(), &auth.Credentials{Tokens: []string{arvadostest.ActiveTokenV2}})
139         ctxUser2 := auth.NewContext(context.Background(), &auth.Credentials{Tokens: []string{arvadostest.SpectatorToken}})
140         ctxAdmin := auth.NewContext(context.Background(), &auth.Credentials{Tokens: []string{arvadostest.AdminToken}})
141         project, err := s.localdb.GroupCreate(ctxUser1, arvados.CreateOptions{
142                 Attrs: map[string]interface{}{
143                         "group_class": "project",
144                 },
145         })
146         c.Assert(err, check.IsNil)
147         c.Check(project.CanWrite, check.Equals, true)
148         c.Check(project.CanManage, check.Equals, true)
149
150         subproject, err := s.localdb.GroupCreate(ctxUser1, arvados.CreateOptions{
151                 Attrs: map[string]interface{}{
152                         "owner_uuid":  project.UUID,
153                         "group_class": "project",
154                 },
155         })
156         c.Assert(err, check.IsNil)
157         c.Check(subproject.CanWrite, check.Equals, true)
158         c.Check(subproject.CanManage, check.Equals, true)
159
160         projlist, err := s.localdb.GroupList(ctxUser1, arvados.ListOptions{
161                 Limit:   -1,
162                 Filters: []arvados.Filter{{"uuid", "in", []string{project.UUID, subproject.UUID}}},
163         })
164         c.Assert(err, check.IsNil)
165         c.Assert(projlist.Items, check.HasLen, 2)
166         for _, p := range projlist.Items {
167                 c.Check(p.CanWrite, check.Equals, true)
168                 c.Check(p.CanManage, check.Equals, true)
169         }
170
171         // Give 2nd user permission to read
172         permlink, err := s.localdb.LinkCreate(ctxAdmin, arvados.CreateOptions{
173                 Attrs: map[string]interface{}{
174                         "link_class": "permission",
175                         "name":       "can_read",
176                         "tail_uuid":  arvadostest.SpectatorUserUUID,
177                         "head_uuid":  project.UUID,
178                 },
179         })
180         c.Assert(err, check.IsNil)
181
182         // As 2nd user: can read, cannot manage, cannot write
183         project2, err := s.localdb.GroupGet(ctxUser2, arvados.GetOptions{UUID: project.UUID})
184         c.Assert(err, check.IsNil)
185         c.Check(project2.CanWrite, check.Equals, false)
186         c.Check(project2.CanManage, check.Equals, false)
187
188         _, err = s.localdb.LinkUpdate(ctxAdmin, arvados.UpdateOptions{
189                 UUID: permlink.UUID,
190                 Attrs: map[string]interface{}{
191                         "name": "can_write",
192                 },
193         })
194         c.Assert(err, check.IsNil)
195
196         // As 2nd user: cannot manage, can write
197         project2, err = s.localdb.GroupGet(ctxUser2, arvados.GetOptions{UUID: project.UUID})
198         c.Assert(err, check.IsNil)
199         c.Check(project2.CanWrite, check.Equals, true)
200         c.Check(project2.CanManage, check.Equals, false)
201
202         // As owner: after freezing, can manage (owner), cannot write (frozen)
203         project, err = s.localdb.GroupUpdate(ctxUser1, arvados.UpdateOptions{
204                 UUID: project.UUID,
205                 Attrs: map[string]interface{}{
206                         "frozen_by_uuid": arvadostest.ActiveUserUUID,
207                 }})
208         c.Assert(err, check.IsNil)
209         c.Check(project.CanWrite, check.Equals, false)
210         c.Check(project.CanManage, check.Equals, true)
211
212         // As admin: can manage (admin), cannot write (frozen)
213         project, err = s.localdb.GroupGet(ctxAdmin, arvados.GetOptions{UUID: project.UUID})
214         c.Assert(err, check.IsNil)
215         c.Check(project.CanWrite, check.Equals, false)
216         c.Check(project.CanManage, check.Equals, true)
217
218         // As 2nd user: cannot manage (perm), cannot write (frozen)
219         project2, err = s.localdb.GroupGet(ctxUser2, arvados.GetOptions{UUID: project.UUID})
220         c.Assert(err, check.IsNil)
221         c.Check(project2.CanWrite, check.Equals, false)
222         c.Check(project2.CanManage, check.Equals, false)
223
224         // After upgrading perm to "manage", as 2nd user: can manage (perm), cannot write (frozen)
225         _, err = s.localdb.LinkUpdate(ctxAdmin, arvados.UpdateOptions{
226                 UUID: permlink.UUID,
227                 Attrs: map[string]interface{}{
228                         "name": "can_manage",
229                 },
230         })
231         c.Assert(err, check.IsNil)
232         project2, err = s.localdb.GroupGet(ctxUser2, arvados.GetOptions{UUID: project.UUID})
233         c.Assert(err, check.IsNil)
234         c.Check(project2.CanWrite, check.Equals, false)
235         c.Check(project2.CanManage, check.Equals, true)
236
237         // 2nd user can also manage (but not write) the subject inside the frozen project
238         subproject2, err := s.localdb.GroupGet(ctxUser2, arvados.GetOptions{UUID: subproject.UUID})
239         c.Assert(err, check.IsNil)
240         c.Check(subproject2.CanWrite, check.Equals, false)
241         c.Check(subproject2.CanManage, check.Equals, true)
242
243         u, err := s.localdb.UserGet(ctxUser1, arvados.GetOptions{
244                 UUID: arvadostest.ActiveUserUUID,
245         })
246         c.Assert(err, check.IsNil)
247         c.Check(u.CanWrite, check.Equals, true)
248         c.Check(u.CanManage, check.Equals, true)
249
250         for _, selectParam := range [][]string{
251                 nil,
252                 {"can_write", "can_manage"},
253         } {
254                 c.Logf("selectParam: %+v", selectParam)
255                 ulist, err := s.localdb.UserList(ctxUser1, arvados.ListOptions{
256                         Limit:   -1,
257                         Filters: []arvados.Filter{{"uuid", "=", arvadostest.ActiveUserUUID}},
258                         Select:  selectParam,
259                 })
260                 c.Assert(err, check.IsNil)
261                 c.Assert(ulist.Items, check.HasLen, 1)
262                 c.Logf("%+v", ulist.Items)
263                 for _, u := range ulist.Items {
264                         c.Check(u.CanWrite, check.Equals, true)
265                         c.Check(u.CanManage, check.Equals, true)
266                 }
267         }
268 }