Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}