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