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}