-
Notifications
You must be signed in to change notification settings - Fork 364
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add p/demo/access (administrable contracts) #880
Closed
Closed
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
81329b7
feat: add p/demo/administrable
moul 6dd6abb
chore: fixup
moul 4528857
chore: fixup
moul 6163c9b
chore: fixup
moul ac38d9c
chore: fixup
moul 3bb7b65
chore: fixup
moul c1b51c0
chore: fixup
moul 85f9cd8
chore: fixup
moul 83b97e4
chore: support unitilized Set
moul 199fbb2
chore: rename to access
moul 8534c03
chore: fixup
moul fab9639
chore: fixup
moul 48e958b
chore: fixup
moul c3e27b6
Merge branch 'master' into dev/moul/administrable
moul f58167a
chore: fixup
moul be4449a
Update examples/gno.land/p/demo/access/set.gno
moul 5277420
Update examples/gno.land/p/demo/access/set.gno
moul 4348288
Update examples/gno.land/p/demo/access/set.gno
moul 837bdd9
Merge branch 'master' into dev/moul/administrable
moul d66bb2b
chore: fixup
moul daf34a0
chore: add assertion
moul 8fe571f
fix: init ctx of injected package when using gno test to support lazy…
moul 5d3d818
chore: fixup
moul 57c4a1c
Merge branch 'master' into dev/moul/administrable
moul d9e0302
chore: fixup
moul 82bb226
Merge branch 'master' into dev/moul/administrable
moul 45da03c
chore: fixup
moul File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// Package access provides a simple access control library for managing authorized addresses and controlling resource access. | ||
// | ||
// Features: authorized address management, access verification. | ||
// | ||
// Note: This package is suitable for basic access control in simple use cases. Consider specialized libraries or frameworks for advanced access control and permission management. | ||
package access // import "gno.land/p/demo/access" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package access | ||
|
||
func ExamplePackageLevel() { | ||
// set this globally or during init() | ||
acl := New() | ||
|
||
// in functions | ||
acl.AssertCurrentHasAccess() | ||
} | ||
|
||
func ExampleEmbedding() { | ||
// declare a new struct, and embed access.Set. | ||
type MyObject struct { | ||
myField int | ||
Set | ||
} | ||
|
||
// initialize the object, it now has its own admin set. | ||
myObject := MyObject{ | ||
myField: 42, | ||
Set: New(), | ||
} | ||
|
||
// check from the context of the object. | ||
myObject.Set.AssertCurrentHasAccess() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module gno.land/p/demo/access | ||
|
||
require ( | ||
"gno.land/p/demo/testutils" v0.0.0-latest | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package access | ||
|
||
import "std" | ||
|
||
// New returns a Set object initialized with the caller as the unique authorized address. | ||
// It is recommended to use this function in the `init()` function of the calling realm. | ||
func New() Set { | ||
addr := std.GetOrigCaller() | ||
return Set{ | ||
addrs: []std.Address{addr}, | ||
} | ||
} | ||
|
||
// NewWithAddress returns a Set object initialized with the provided address as authorized. | ||
func NewWithAddress(addr std.Address) Set { | ||
return Set{ | ||
addrs: []std.Address{addr}, | ||
} | ||
} | ||
|
||
// Set is an object containing the configuration and allowing the application of filters. | ||
// It is suited to be used as a contract-side global variable or can be embedded in another Go object. | ||
type Set struct { | ||
addrs []std.Address | ||
} | ||
|
||
// List returns a slice containing all the authorized addresses. | ||
func (s *Set) List() []std.Address { | ||
return s.addrs | ||
} | ||
|
||
// HasAccess checks if the provided address is in the list of authorized ones. | ||
func (s *Set) HasAccess(addr std.Address) bool { | ||
for _, entry := range s.addrs { | ||
if entry == addr { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// CurrentHasAccess checks if the caller or prevRealm is authorized. | ||
func (s *Set) CurrentHasAccess() bool { | ||
if s.HasAccess(std.GetOrigCaller()) { | ||
return true | ||
} | ||
|
||
// XXX: also check for std.PrevRealm, when merged. | ||
|
||
return false | ||
} | ||
|
||
// AssertCurrentHasAccess checks whether the std.GetOrigCaller or std.PrevRealm is whitelisted as authorized. | ||
// If not, it panics indicating restricted access. | ||
func (s *Set) AssertCurrentHasAccess() { | ||
if !s.CurrentHasAccess() { | ||
panic("restricted access.") | ||
} | ||
} | ||
|
||
// Add adds an address to the list of authorized addresses. | ||
// It requires the caller or prevRealm to be authorized, otherwise, it panics. | ||
func (s *Set) Add(addr std.Address) { | ||
if s.HasAccess(addr) { | ||
// XXX: Consider panicking or handling duplicate addition differently. | ||
return | ||
} | ||
Comment on lines
+64
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would use a specific method to check duplicates, or a different data structure instead of using a method that is doing the same now, but it might not in the future. |
||
s.addrs = append(s.addrs, addr) | ||
} | ||
|
||
// ForceDel removes an address from the list of authorized addresses. | ||
// It won't panic if there is only one address. | ||
// It requires the caller or prevRealm to be authorized, otherwise, it panics. | ||
func (s *Set) ForceDel(addr std.Address) (success bool) { | ||
for i, entry := range s.addrs { | ||
if entry == addr { | ||
s.addrs = append(s.addrs[:i], s.addrs[i+1:]...) | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// Del removes an address from the list of authorized addresses. | ||
// It requires the caller or prevRealm to be authorized, otherwise, it panics. | ||
func (s *Set) Del(addr std.Address) (success bool) { | ||
if len(s.addrs) == 0 { | ||
panic("should not happen") | ||
} | ||
|
||
// XXX: should we prevent deleting self? | ||
|
||
// Prevent deleting the last authorized address. | ||
if len(s.addrs) == 1 { | ||
moul marked this conversation as resolved.
Show resolved
Hide resolved
|
||
panic("cannot have no authorized address.") | ||
} | ||
return s.ForceDel(addr) | ||
} | ||
|
||
// ReplaceAll removes all existing authorized addresses and replaces them with a new one. | ||
// It requires the caller or prevRealm to be authorized, otherwise, it panics. | ||
func (s *Set) Replace(addrs []std.Address) { | ||
s.addrs = addrs | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package access | ||
|
||
import ( | ||
"std" | ||
"testing" | ||
|
||
"gno.land/p/demo/testutils" | ||
) | ||
|
||
func TestSet_AddAndRemoveAdmin(t *testing.T) { | ||
set := New() | ||
|
||
// Add an admin | ||
admin1 := testutils.TestAddress("test1") | ||
set.Add(admin1) | ||
if !set.HasAccess(admin1) { | ||
t.Errorf("Expected admin1 to be added, but it was not") | ||
} | ||
|
||
// Remove the admin | ||
set.Del(admin1) | ||
if set.HasAccess(admin1) { | ||
t.Errorf("Expected admin1 to be removed, but it was still present") | ||
} | ||
} | ||
|
||
func TestSet_ListAdmins(t *testing.T) { | ||
// Add multiple admins | ||
admin1 := testutils.TestAddress("test2") | ||
admin2 := testutils.TestAddress("test3") | ||
set := NewWithAddress(admin1) | ||
std.TestSetOrigCaller(admin1) | ||
set.Add(admin2) | ||
|
||
// Get the list of admins | ||
admins := set.List() | ||
|
||
// Verify the correct number of admins | ||
expectedAdminsCount := 2 | ||
if len(admins) != expectedAdminsCount { | ||
t.Errorf("Expected %d admins, but got %d", expectedAdminsCount, len(admins)) | ||
} | ||
|
||
// Verify the admins in the list | ||
expectedAdmins := []std.Address{admin1, admin2} | ||
for _, expectedAdmin := range expectedAdmins { | ||
found := false | ||
for _, admin := range admins { | ||
if admin == expectedAdmin { | ||
found = true | ||
break | ||
} | ||
} | ||
if !found { | ||
t.Errorf("Expected admin %s to be in the list, but it was not found", expectedAdmin) | ||
} | ||
} | ||
} | ||
|
||
func TestSet_CurrentHasAccess(t *testing.T) { | ||
// Set the original caller as admin | ||
admin := testutils.TestAddress("test4") | ||
std.TestSetOrigCaller(admin) | ||
set := New() | ||
|
||
// Verify CurrentHasAccess returns true for the admin | ||
if !set.CurrentHasAccess() { | ||
t.Errorf("Expected CurrentHasAccess to return true for the admin, but it returned false") | ||
} | ||
|
||
// Verify CurrentHasAccess returns false for a non-admin | ||
nonAdmin := testutils.TestAddress("test5") | ||
std.TestSetOrigCaller(nonAdmin) | ||
if set.CurrentHasAccess() { | ||
t.Errorf("Expected CurrentHasAccess to return false for a non-admin, but it returned true") | ||
} | ||
} | ||
|
||
func TestSet_AddAndRemoveAdmin_WontPanic(t *testing.T) { | ||
// Add an admin | ||
admin := testutils.TestAddress("test6") | ||
std.TestSetOrigCaller(admin) | ||
set := New() | ||
|
||
// Set a non-admin caller | ||
nonAdmin := testutils.TestAddress("test7") | ||
std.TestSetOrigCaller(nonAdmin) | ||
|
||
// Verify Add won't panic in Privileged mode | ||
set.Add(nonAdmin) | ||
|
||
// Verify Del won't panic in Privileged mode | ||
set.Del(nonAdmin) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package access | ||
|
||
import "std" | ||
|
||
type UnprivilegedSet struct { | ||
Set | ||
} | ||
|
||
// Unprivileged returns an unprivileged structure that can be securely exposed as object. | ||
func (s Set) Unprivileged() UnprivilegedSet { | ||
return UnprivilegedSet{Set: s} | ||
} | ||
|
||
func (u *UnprivilegedSet) Add(addr std.Address) { | ||
u.AssertCurrentHasAccess() | ||
u.Set.Add(addr) | ||
} | ||
|
||
func (u *UnprivilegedSet) Del(addr std.Address) { | ||
u.AssertCurrentHasAccess() | ||
u.Set.Del(addr) | ||
} | ||
|
||
func (u *UnprivilegedSet) ForceDel(addr std.Address) { | ||
u.AssertCurrentHasAccess() | ||
u.Set.ForceDel(addr) | ||
} | ||
|
||
func (u *UnprivilegedSet) Replace(addr std.Address) { | ||
u.AssertCurrentHasAccess() | ||
addrs := []std.Address{addr} | ||
u.Set.Replace(addrs) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package access | ||
|
||
import ( | ||
"std" | ||
"testing" | ||
|
||
"gno.land/p/demo/testutils" | ||
) | ||
|
||
func TestUnprivilegedSet_AddAndRemoveAdmin(t *testing.T) { | ||
set := New().Unprivileged() | ||
|
||
// Add an admin | ||
admin1 := testutils.TestAddress("test1") | ||
set.Add(admin1) | ||
if !set.HasAccess(admin1) { | ||
t.Errorf("Expected admin1 to be added, but it was not") | ||
} | ||
|
||
// Remove the admin | ||
set.Del(admin1) | ||
if set.HasAccess(admin1) { | ||
t.Errorf("Expected admin1 to be removed, but it was still present") | ||
} | ||
} | ||
|
||
func TestUnprivilegedSet_ListAdmins(t *testing.T) { | ||
// Add multiple admins | ||
admin1 := testutils.TestAddress("test2") | ||
admin2 := testutils.TestAddress("test3") | ||
set := NewWithAddress(admin1).Unprivileged() | ||
std.TestSetOrigCaller(admin1) | ||
set.Add(admin2) | ||
|
||
// Get the list of admins | ||
admins := set.List() | ||
|
||
// Verify the correct number of admins | ||
expectedAdminsCount := 2 | ||
if len(admins) != expectedAdminsCount { | ||
t.Errorf("Expected %d admins, but got %d", expectedAdminsCount, len(admins)) | ||
} | ||
|
||
// Verify the admins in the list | ||
expectedAdmins := []std.Address{admin1, admin2} | ||
for _, expectedAdmin := range expectedAdmins { | ||
found := false | ||
for _, admin := range admins { | ||
if admin == expectedAdmin { | ||
found = true | ||
break | ||
} | ||
} | ||
if !found { | ||
t.Errorf("Expected admin %s to be in the list, but it was not found", expectedAdmin) | ||
} | ||
} | ||
} | ||
|
||
func TestUnprivilegedSet_CurrentHasAccess(t *testing.T) { | ||
// Set the original caller as admin | ||
admin := testutils.TestAddress("test4") | ||
std.TestSetOrigCaller(admin) | ||
set := New().Unprivileged() | ||
|
||
// Verify CurrentHasAccess returns true for the admin | ||
if !set.CurrentHasAccess() { | ||
t.Errorf("Expected CurrentHasAccess to return true for the admin, but it returned false") | ||
} | ||
|
||
// Verify CurrentHasAccess returns false for a non-admin | ||
nonAdmin := testutils.TestAddress("test5") | ||
std.TestSetOrigCaller(nonAdmin) | ||
if set.CurrentHasAccess() { | ||
t.Errorf("Expected CurrentHasAccess to return false for a non-admin, but it returned true") | ||
} | ||
} | ||
|
||
func TestUnprivilegedSet_AddAndRemoveAdmin_Panic(t *testing.T) { | ||
// Add an admin | ||
admin := testutils.TestAddress("test6") | ||
std.TestSetOrigCaller(admin) | ||
set := New().Unprivileged() | ||
|
||
// Set a non-admin caller | ||
nonAdmin := testutils.TestAddress("test7") | ||
std.TestSetOrigCaller(nonAdmin) | ||
|
||
// Verify Add panics when called by a non-admin | ||
defer func() { | ||
if r := recover(); r == nil { | ||
t.Errorf("Expected Add to panic when called by a non-admin, but it did not panic") | ||
} | ||
}() | ||
set.Add(admin) | ||
|
||
// Verify Del panics when called by a non-admin | ||
defer func() { | ||
if r := recover(); r == nil { | ||
t.Errorf("Expected Del to panic when called by a non-admin, but it did not panic") | ||
} | ||
}() | ||
set.Del(admin) | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have a better naming?
Set
is a very generic name.Here's few suggestions:
AuthorizationSet
: Describes that it holds a list of authorized addresses.AuthSet
: Short for "Authorization Set", indicating its purpose of holding authorized addresses.AccessControl
: Suggests that it is used for controlling access to certain resources.WhiteList
: Suggests that it acts as a whitelist for permitted addresses.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or maybe?
Since this struct only contains one field
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about having something like this
It can provide better access control.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The expected usage is:
access.Set
which IMO makes sense for such specialized pure packages.However, I agree with you in other cases, especially apps or just big libraries.