authz_test.gno
18.88 Kb ยท 600 lines
1package authz
2
3import (
4 "chain"
5 "chain/runtime"
6 "errors"
7 "strings"
8 "testing"
9
10 "gno.land/p/nt/testutils"
11 "gno.land/p/nt/uassert"
12)
13
14func TestNewWithCurrent(t *testing.T) {
15 alice := testutils.TestAddress("alice")
16 testing.SetRealm(testing.NewUserRealm(alice))
17
18 auth := NewWithCurrent()
19
20 // Check that the current authority is a MemberAuthority
21 memberAuth, ok := auth.Authority().(*MemberAuthority)
22 uassert.True(t, ok, "expected MemberAuthority")
23
24 // Check that the caller is a member
25 uassert.True(t, memberAuth.Has(alice), "caller should be a member")
26
27 // Check string representation
28 uassert.True(t, strings.Contains(auth.String(), alice.String()))
29}
30
31func TestNewWithAuthority(t *testing.T) {
32 alice := testutils.TestAddress("alice")
33 memberAuth := NewMemberAuthority(alice)
34
35 auth := NewWithAuthority(memberAuth)
36
37 // Check that the current authority is the one we provided
38 uassert.True(t, auth.Authority() == memberAuth, "expected provided authority")
39}
40
41func TestAuthorizerAuthorize(t *testing.T) {
42 alice := testutils.TestAddress("alice")
43 testing.SetRealm(testing.NewUserRealm(alice))
44
45 auth := NewWithCurrent()
46
47 // Test successful action with args
48 executed := false
49 args := []any{"test_arg", 123}
50 err := auth.DoByCurrent("test_action", func() error {
51 executed = true
52 return nil
53 }, args...)
54
55 uassert.True(t, err == nil, "expected no error")
56 uassert.True(t, executed, "action should have been executed")
57
58 // Test unauthorized action with args
59 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("bob")))
60
61 executed = false
62 err = auth.DoByCurrent("test_action", func() error {
63 executed = true
64 return nil
65 }, "unauthorized_arg")
66
67 uassert.True(t, err != nil, "expected error")
68 uassert.False(t, executed, "action should not have been executed")
69
70 // Test action returning error
71 testing.SetRealm(testing.NewUserRealm(alice))
72 expectedErr := errors.New("test error")
73
74 err = auth.DoByCurrent("test_action", func() error {
75 return expectedErr
76 })
77
78 uassert.True(t, err == expectedErr, "expected specific error")
79}
80
81func TestAuthorizerTransfer(t *testing.T) {
82 alice := testutils.TestAddress("alice")
83 testing.SetRealm(testing.NewUserRealm(alice))
84
85 auth := NewWithCurrent()
86
87 // Test transfer to new member authority
88 bob := testutils.TestAddress("bob")
89 newAuth := NewMemberAuthority(bob)
90
91 err := auth.Transfer(alice, newAuth)
92 uassert.True(t, err == nil, "expected no error")
93 uassert.True(t, auth.Authority() == newAuth, "expected new authority")
94
95 // Test unauthorized transfer
96 testing.SetRealm(testing.NewUserRealm(bob)) // doesn't matter that it's bob
97 carol := testutils.TestAddress("carol")
98
99 err = auth.Transfer(carol, NewMemberAuthority(alice))
100 uassert.True(t, err != nil, "expected error")
101
102 // Test transfer to contract authority
103 contractAuth := NewContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error {
104 return action()
105 })
106
107 err = auth.Transfer(bob, contractAuth)
108 uassert.True(t, err == nil, "expected no error")
109 uassert.True(t, auth.Authority() == contractAuth, "expected contract authority")
110}
111
112func TestAuthorizerTransferChain(t *testing.T) {
113 alice := testutils.TestAddress("alice")
114 testing.SetRealm(testing.NewUserRealm(alice))
115
116 // Create a chain of transfers
117 auth := NewWithCurrent()
118
119 // First transfer to a new member authority
120 bob := testutils.TestAddress("bob")
121 memberAuth := NewMemberAuthority(bob)
122
123 err := auth.Transfer(alice, memberAuth)
124 uassert.True(t, err == nil, "unexpected error in first transfer")
125
126 // Then transfer to a contract authority
127 contractAuth := NewContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error {
128 return action()
129 })
130 err = auth.Transfer(bob, contractAuth)
131 uassert.True(t, err == nil, "unexpected error in second transfer")
132
133 // Finally transfer to an auto-accept authority
134 autoAuth := NewAutoAcceptAuthority()
135 codeRealm := testing.NewCodeRealm("gno.land/r/test")
136 code := codeRealm.Address()
137 testing.SetRealm(codeRealm)
138 err = auth.Transfer(code, autoAuth)
139 uassert.True(t, err == nil, "unexpected error in final transfer")
140 uassert.True(t, auth.Authority() == autoAuth, "expected auto-accept authority")
141}
142
143func TestAuthorizerTransferVulnerability(t *testing.T) {
144 admin := testutils.TestAddress("admin")
145 attacker := testutils.TestAddress("attacker")
146
147 // Setup: Authorizer controlled by 'admin'
148 testing.SetRealm(testing.NewUserRealm(admin))
149 auth := NewWithCurrent() // 'admin' is the initial authority
150
151 // Check initial state
152 initialAuth, ok := auth.Authority().(*MemberAuthority)
153 uassert.True(t, ok)
154 uassert.True(t, initialAuth.Has(admin))
155 uassert.False(t, initialAuth.Has(attacker))
156
157 // Simulate attacker's context
158 testing.SetRealm(testing.NewUserRealm(attacker))
159
160 // Vulnerability: Attacker calls Transfer, providing 'admin' as the 'caller'
161 // argument, even though the actual caller is 'attacker'.
162 attackerAuth := NewMemberAuthority(attacker)
163 err := auth.Transfer(admin, attackerAuth) // Vulnerability point
164
165 // Assertions:
166 // 1. Transfer should succeed if vulnerability exists (checks only provided 'caller').
167 // NOTE: If this test FAILS, the vulnerability is likely fixed!
168 uassert.NoError(t, err, "transfer should succeed due to vulnerability")
169
170 // 2. Authority should now be the attacker's.
171 finalAuth, ok := auth.Authority().(*MemberAuthority)
172 uassert.True(t, ok)
173 uassert.True(t, finalAuth == attackerAuth)
174
175 // 3. Attacker should be the sole member.
176 uassert.True(t, finalAuth.Has(attacker))
177 uassert.False(t, finalAuth.Has(admin))
178
179 // Verify attacker can now perform actions
180 actionExecuted := false
181 err = auth.DoByCurrent("attacker_action", func() error {
182 actionExecuted = true
183 return nil
184 })
185 uassert.NoError(t, err)
186 uassert.True(t, actionExecuted)
187}
188
189func TestAuthorizerWithDroppedAuthority(t *testing.T) {
190 alice := testutils.TestAddress("alice")
191 testing.SetRealm(testing.NewUserRealm(alice))
192
193 auth := NewWithCurrent()
194
195 // Transfer to dropped authority
196 err := auth.Transfer(alice, NewDroppedAuthority())
197 uassert.True(t, err == nil, "expected no error")
198
199 // Try to execute action
200 err = auth.DoByCurrent("test_action", func() error {
201 return nil
202 })
203 uassert.True(t, err != nil, "expected error from dropped authority")
204
205 // Try to transfer again
206 err = auth.Transfer(alice, NewMemberAuthority(alice))
207 uassert.True(t, err != nil, "expected error when transferring from dropped authority")
208}
209
210func TestContractAuthorityHandlerExecutionOnce(t *testing.T) {
211 attempts := 0
212 executed := 0
213
214 contractAuth := NewContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error {
215 // Try to execute the action twice in the same handler
216 if err := action(); err != nil {
217 return err
218 }
219 attempts++
220
221 // Second execution should fail
222 if err := action(); err != nil {
223 return err
224 }
225 attempts++
226 return nil
227 })
228
229 // Set caller to contract address
230 codeRealm := testing.NewCodeRealm("gno.land/r/test")
231 testing.SetRealm(codeRealm)
232 code := codeRealm.Address()
233
234 testArgs := []any{"proposal_id", 42, "metadata", map[string]string{"key": "value"}}
235 err := contractAuth.Authorize(code, "test_action", func() error {
236 executed++
237 return nil
238 }, testArgs...)
239
240 uassert.True(t, err == nil, "handler execution should succeed")
241 uassert.True(t, attempts == 2, "handler should have attempted execution twice")
242 uassert.True(t, executed == 1, "handler should have executed once")
243}
244
245func TestContractAuthorityExecutionTwice(t *testing.T) {
246 executed := 0
247
248 contractAuth := NewContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error {
249 return action()
250 })
251
252 // Set caller to contract address
253 codeRealm := testing.NewCodeRealm("gno.land/r/test")
254 testing.SetRealm(codeRealm)
255 code := codeRealm.Address()
256 testArgs := []any{"proposal_id", 42, "metadata", map[string]string{"key": "value"}}
257
258 err := contractAuth.Authorize(code, "test_action", func() error {
259 executed++
260 return nil
261 }, testArgs...)
262
263 uassert.True(t, err == nil, "handler execution should succeed")
264 uassert.True(t, executed == 1, "handler should have executed once")
265
266 // A new action, even with the same title, should be executed
267 err = contractAuth.Authorize(code, "test_action", func() error {
268 executed++
269 return nil
270 }, testArgs...)
271
272 uassert.True(t, err == nil, "handler execution should succeed")
273 uassert.True(t, executed == 2, "handler should have executed twice")
274}
275
276func TestContractAuthorityWithProposer(t *testing.T) {
277 alice := testutils.TestAddress("alice")
278 memberAuth := NewMemberAuthority(alice)
279
280 handlerCalled := false
281 actionExecuted := false
282
283 contractAuth := NewRestrictedContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error {
284 handlerCalled = true
285 // Set caller to contract address before executing action
286 testing.SetRealm(testing.NewCodeRealm("gno.land/r/test"))
287 return action()
288 }, memberAuth)
289
290 // Test authorized member
291 testArgs := []any{"proposal_metadata", "test value"}
292 err := contractAuth.Authorize(alice, "test_action", func() error {
293 actionExecuted = true
294 return nil
295 }, testArgs...)
296
297 uassert.True(t, err == nil, "authorized member should be able to propose")
298 uassert.True(t, handlerCalled, "contract handler should be called")
299 uassert.True(t, actionExecuted, "action should be executed")
300
301 // Reset flags for unauthorized test
302 handlerCalled = false
303 actionExecuted = false
304
305 // Test unauthorized proposer
306 bob := testutils.TestAddress("bob")
307 err = contractAuth.Authorize(bob, "test_action", func() error {
308 actionExecuted = true
309 return nil
310 }, testArgs...)
311
312 uassert.True(t, err != nil, "unauthorized member should not be able to propose")
313 uassert.False(t, handlerCalled, "contract handler should not be called for unauthorized proposer")
314 uassert.False(t, actionExecuted, "action should not be executed for unauthorized proposer")
315}
316
317func TestAutoAcceptAuthority(t *testing.T) {
318 alice := testutils.TestAddress("alice")
319 auth := NewAutoAcceptAuthority()
320
321 // Test that any action is authorized
322 executed := false
323 err := auth.Authorize(alice, "test_action", func() error {
324 executed = true
325 return nil
326 })
327
328 uassert.True(t, err == nil, "auto-accept should not return error")
329 uassert.True(t, executed, "action should have been executed")
330
331 // Test with different caller
332 random := testutils.TestAddress("random")
333 executed = false
334 err = auth.Authorize(random, "test_action", func() error {
335 executed = true
336 return nil
337 })
338
339 uassert.True(t, err == nil, "auto-accept should not care about caller")
340 uassert.True(t, executed, "action should have been executed")
341}
342
343func TestAutoAcceptAuthorityWithArgs(t *testing.T) {
344 auth := NewAutoAcceptAuthority()
345 anyuser := testutils.TestAddress("anyuser")
346
347 // Test that any action is authorized with args
348 executed := false
349 testArgs := []any{"arg1", 42, "arg3"}
350 err := auth.Authorize(anyuser, "test_action", func() error {
351 executed = true
352 return nil
353 }, testArgs...)
354
355 uassert.True(t, err == nil, "auto-accept should not return error")
356 uassert.True(t, executed, "action should have been executed")
357}
358
359func TestMemberAuthorityMultipleMembers(t *testing.T) {
360 alice := testutils.TestAddress("alice")
361 bob := testutils.TestAddress("bob")
362 carol := testutils.TestAddress("carol")
363
364 // Create authority with multiple members
365 auth := NewMemberAuthority(alice, bob)
366
367 // Test that both members can execute actions
368 for _, member := range []address{alice, bob} {
369 err := auth.Authorize(member, "test_action", func() error {
370 return nil
371 })
372 uassert.True(t, err == nil, "member should be authorized")
373 }
374
375 // Test that non-member cannot execute
376 err := auth.Authorize(carol, "test_action", func() error {
377 return nil
378 })
379 uassert.True(t, err != nil, "non-member should not be authorized")
380
381 // Test Tree() functionality
382 tree := auth.Tree()
383 uassert.True(t, tree.Size() == 2, "tree should have 2 members")
384
385 // Verify both members are in the tree
386 found := make(map[address]bool)
387 tree.Iterate("", "", func(key string, _ any) bool {
388 found[address(key)] = true
389 return false
390 })
391 uassert.True(t, found[alice], "alice should be in the tree")
392 uassert.True(t, found[bob], "bob should be in the tree")
393 uassert.False(t, found[carol], "carol should not be in the tree")
394
395 // Test read-only nature of the tree
396 defer func() {
397 r := recover()
398 uassert.True(t, r != nil, "modifying read-only tree should panic")
399 }()
400 tree.Set(string(carol), nil) // This should panic
401}
402
403func TestAuthorizerCurrentNeverNil(t *testing.T) {
404 auth := NewWithCurrent()
405 addr := runtime.CurrentRealm().Address()
406
407 // Authority should never be nil after initialization
408 uassert.True(t, auth.Authority() != nil, "current authority should not be nil")
409
410 // Authority should not be nil after transfer
411 err := auth.Transfer(addr, NewAutoAcceptAuthority())
412 uassert.True(t, err == nil, "transfer should succeed")
413 uassert.True(t, auth.Authority() != nil, "current authority should not be nil after transfer")
414}
415
416func TestContractAuthorityValidation(t *testing.T) {
417 /*
418 // Test empty path - should panic
419 panicked := false
420 func() {
421 defer func() {
422 if r := recover(); r != nil {
423 panicked = true
424 }
425 }()
426 NewContractAuthority("", nil)
427 }()
428 uassert.True(t, panicked, "expected panic for empty path")
429 */
430
431 // Test nil handler - should return error on Authorize
432 auth := NewContractAuthority("gno.land/r/test", nil)
433 code := testing.NewCodeRealm("gno.land/r/test").Address()
434 err := auth.Authorize(code, "test", func() error {
435 return nil
436 })
437 uassert.True(t, err != nil, "nil handler authority should fail to authorize")
438
439 // Test valid configuration
440 handler := func(title string, action PrivilegedAction) error {
441 return nil
442 }
443 contractAuth := NewContractAuthority("gno.land/r/test", handler)
444 err = contractAuth.Authorize(code, "test", func() error {
445 return nil
446 })
447 uassert.True(t, err == nil, "valid contract authority should authorize successfully")
448}
449
450func TestAuthorizerString(t *testing.T) {
451 auth := NewWithCurrent()
452 addr := runtime.CurrentRealm().Address()
453
454 // Test initial string representation
455 str := auth.String()
456 uassert.Equal(t, str, "member_authority[g134ru6z8r00teg3r342h3yqf9y55mztdvlj4758]")
457
458 // Test string after transfer
459 autoAuth := NewAutoAcceptAuthority()
460 err := auth.Transfer(addr, autoAuth)
461 uassert.True(t, err == nil, "transfer should succeed")
462 str = auth.String()
463 uassert.Equal(t, str, "auto_accept_authority")
464
465 // Test custom authority
466 customAuth := &mockAuthority{}
467 bob := testutils.TestAddress("bob")
468 err = auth.Transfer(bob, customAuth)
469 uassert.True(t, err == nil, "transfer should succeed")
470 str = auth.String()
471 uassert.Equal(t, str, "custom_authority[mock]")
472}
473
474type mockAuthority struct{}
475
476func (c mockAuthority) String() string { return "mock" }
477func (a mockAuthority) Authorize(caller address, title string, action PrivilegedAction, args ...any) error {
478 // autoaccept
479 return action()
480}
481
482func TestAuthorityString(t *testing.T) {
483 alice := testutils.TestAddress("alice")
484
485 // MemberAuthority
486 memberAuth := NewMemberAuthority(alice)
487 memberStr := memberAuth.String()
488 expectedMemberStr := "member_authority[g1v9kxjcm9ta047h6lta047h6lta047h6lzd40gh]"
489 uassert.Equal(t, memberStr, expectedMemberStr)
490
491 // ContractAuthority
492 contractAuth := NewContractAuthority("gno.land/r/test", func(title string, action PrivilegedAction) error { return nil })
493 contractStr := contractAuth.String()
494 expectedContractStr := "contract_authority[contract=gno.land/r/test]"
495 uassert.Equal(t, contractStr, expectedContractStr)
496
497 // AutoAcceptAuthority
498 autoAuth := NewAutoAcceptAuthority()
499 autoStr := autoAuth.String()
500 expectedAutoStr := "auto_accept_authority"
501 uassert.Equal(t, autoStr, expectedAutoStr)
502
503 // DroppedAuthority
504 droppedAuth := NewDroppedAuthority()
505 droppedStr := droppedAuth.String()
506 expectedDroppedStr := "dropped_authority"
507 uassert.Equal(t, droppedStr, expectedDroppedStr)
508}
509
510func TestContractAuthorityUnauthorizedCaller(t *testing.T) {
511 contractPath := "gno.land/r/testcontract"
512 contractAddr := chain.PackageAddress(contractPath)
513 unauthorizedAddr := testutils.TestAddress("unauthorized")
514
515 // Handler that checks the caller before proceeding
516 handlerExecutedCorrectly := false // Tracks if handler logic ran correctly
517 handlerErrorMsg := "handler: caller is not the contract"
518 contractHandler := func(title string, action PrivilegedAction) error {
519 caller := runtime.CurrentRealm().Address()
520 if caller != contractAddr {
521 return errors.New(handlerErrorMsg)
522 }
523 // Only execute action and mark success if caller is correct
524 handlerExecutedCorrectly = true
525 return action()
526 }
527
528 contractAuth := NewContractAuthority(contractPath, contractHandler)
529 authorizer := NewWithAuthority(contractAuth) // Start with ContractAuthority
530
531 actionExecuted := false
532 privilegedAction := func() error {
533 actionExecuted = true
534 return nil
535 }
536
537 // 1. Attempt action from unauthorized user
538 testing.SetRealm(testing.NewUserRealm(unauthorizedAddr))
539 err := authorizer.DoByCurrent("test_action_unauthorized", privilegedAction)
540
541 // Assertions for unauthorized call
542 uassert.Error(t, err, "DoByCurrent should return an error for unauthorized caller")
543 uassert.ErrorContains(t, err, handlerErrorMsg, "Error should originate from the handler check")
544 uassert.False(t, handlerExecutedCorrectly, "Handler should not have executed successfully for unauthorized caller")
545 uassert.False(t, actionExecuted, "Privileged action should not have executed for unauthorized caller")
546
547 // 2. Attempt action from the correct contract
548 handlerExecutedCorrectly = false // Reset flag
549 actionExecuted = false // Reset flag
550 testing.SetRealm(testing.NewCodeRealm(contractPath))
551 err = authorizer.DoByCurrent("test_action_authorized", privilegedAction)
552
553 // Assertions for authorized call
554 uassert.NoError(t, err, "DoByCurrent should succeed for authorized contract caller")
555 uassert.True(t, handlerExecutedCorrectly, "Handler should have executed successfully for authorized caller")
556 uassert.True(t, actionExecuted, "Privileged action should have executed for authorized caller")
557}
558
559func crossThrough(rlm runtime.Realm, cr func()) {
560 testing.SetRealm(rlm)
561 cr()
562}
563
564func TestAuthorizerDoByPrevious(t *testing.T) {
565 alice := testutils.TestAddress("alice")
566 bob := testutils.TestAddress("bob")
567
568 auth := NewWithMembers(alice)
569
570 testing.SetRealm(testing.NewUserRealm(alice))
571
572 crossThrough(testing.NewCodeRealm("gno.land/r/test/test"), func() {
573 executed := false
574 args := []any{"test_arg", 123}
575 err := auth.DoByPrevious("test_action", func() error {
576 executed = true
577 return nil
578 }, args...)
579 uassert.NoError(t, err, "expected no error")
580 uassert.True(t, executed, "action should have been executed")
581
582 expectedErr := errors.New("test error")
583 err = auth.DoByPrevious("test_action", func() error {
584 return expectedErr
585 })
586 uassert.ErrorContains(t, err, expectedErr.Error(), "expected error")
587 })
588
589 testing.SetRealm(testing.NewUserRealm(bob))
590 crossThrough(testing.NewCodeRealm("gno.land/r/test/test"), func() {
591 executed := false
592 err := auth.DoByPrevious("test_action", func() error {
593 executed = true
594 return nil
595 }, "unauthorized_arg")
596
597 uassert.ErrorContains(t, err, "unauthorized", "expected error")
598 uassert.False(t, executed, "action should not have been executed")
599 })
600}