Skip to content
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(examples): p/demo/access #2741

Draft
wants to merge 32 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
81329b7
feat: add p/demo/administrable
moul Jun 8, 2023
6dd6abb
chore: fixup
moul Jun 8, 2023
4528857
chore: fixup
moul Jun 8, 2023
6163c9b
chore: fixup
moul Jun 8, 2023
ac38d9c
chore: fixup
moul Jun 8, 2023
3bb7b65
chore: fixup
moul Jun 8, 2023
c1b51c0
chore: fixup
moul Jun 8, 2023
85f9cd8
chore: fixup
moul Jun 8, 2023
83b97e4
chore: support unitilized Set
moul Jun 8, 2023
199fbb2
chore: rename to access
moul Jun 8, 2023
8534c03
chore: fixup
moul Jun 8, 2023
fab9639
chore: fixup
moul Jun 8, 2023
48e958b
chore: fixup
moul Jun 8, 2023
c3e27b6
Merge branch 'master' into dev/moul/administrable
moul Jun 20, 2023
f58167a
chore: fixup
moul Jun 20, 2023
be4449a
Update examples/gno.land/p/demo/access/set.gno
moul Oct 20, 2023
5277420
Update examples/gno.land/p/demo/access/set.gno
moul Oct 20, 2023
4348288
Update examples/gno.land/p/demo/access/set.gno
moul Oct 20, 2023
837bdd9
Merge branch 'master' into dev/moul/administrable
moul Oct 20, 2023
d66bb2b
chore: fixup
moul Oct 20, 2023
daf34a0
chore: add assertion
moul Oct 20, 2023
8fe571f
fix: init ctx of injected package when using gno test to support lazy…
moul Oct 20, 2023
5d3d818
chore: fixup
moul Oct 21, 2023
57c4a1c
Merge branch 'master' into dev/moul/administrable
moul Jan 23, 2024
d9e0302
chore: fixup
moul Jun 12, 2024
82bb226
Merge branch 'master' into dev/moul/administrable
moul Jun 12, 2024
45da03c
chore: fixup
moul Jun 12, 2024
9adb510
Merge branch 'master' into leon/administrable
leohhhn Aug 28, 2024
7d878bb
reset
leohhhn Sep 12, 2024
2b13382
save
leohhhn Sep 17, 2024
6ec1d3b
Merge branch 'master' into leon/administrable
leohhhn Sep 20, 2024
821cdf8
save
leohhhn Sep 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions examples/gno.land/p/demo/access/doc.gno
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"
9 changes: 9 additions & 0 deletions examples/gno.land/p/demo/access/errors.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package access

import "errors"

var (
ErrNotInSet = errors.New("access: address not in set")
ErrNoMembersInSet = errors.New("access: set is empty")
ErrLastMember = errors.New("access: cannot delete last member")
)
26 changes: 26 additions & 0 deletions examples/gno.land/p/demo/access/example_test.gno
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()
}
5 changes: 5 additions & 0 deletions examples/gno.land/p/demo/access/gno.mod
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
)
115 changes: 115 additions & 0 deletions examples/gno.land/p/demo/access/set.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package access

import (
"github.com/gnolang/gno/examples/gno.land/p/demo/avl"
"github.com/gnolang/gno/examples/gno.land/p/demo/ownable"
"github.com/gnolang/gno/gnovm/stdlibs/std"
)

// 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 {
*ownable.Ownable // owner in ownable is superuser
authorized *avl.Tree // std.Addr > struct{}{}
}

// 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 {
s := &Set{
ownable.New(),
avl.NewTree(),
}

// Add owner to auth list
s.authorized.Set(a.Owner().String(), struct{}{})
return s
}

// NewWithAddress returns a Set object initialized with the provided address as authorized
func NewWithAddress(addr std.Address) *Set {
s := &Set{
ownable.NewWithAddress(addr),
avl.NewTree(),
}

// Add owner to the set
s.authorized.Set(a.Owner().String(), struct{}{})
return s
}

// List returns a list containing all the authorized addresses
func (s Set) List() []std.Address {
var addrs []std.Address
if s.authorized.Size() == 0 {
return addrs
}
// todo test
s.authorized.Iterate("", "", func(key string, value interface{}) bool {
if value == struct{}{} {
addrs = append(addrs, std.Address(key))
}
return false
})

return addrs
}

// HasAccess checks if the provided address is in the list of authorized ones.
func (s Set) HasAccess(addr std.Address) bool {
return s.authorized.Has(addr.String())
}

// CallerHasAccess checks if the caller or prevRealm is authorized.
func (s Set) CallerHasAccess() bool {
return s.HasAccess(std.PrevRealm().Addr())
}

// AssertCallerHasAccess checks whether the std.GetOrigCaller or std.PrevRealm is whitelisted as authorized.
// If not, it panics indicating restricted access.
func (s *Set) AssertCallerHasAccess() {
if !s.CallerHasAccess() {
panic("access: caller is not authorized")
}
}

// 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) {
panic("access: addr already has access")
}

s.authorized.Set(addr.String(), struct{}{})
}

// 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) error {
if s.authorized.Size() == 0 {
return ErrNoMembersInSet
}

if s.authorized.Size() == 1 {
return ErrLastMember
}

return s.ForceDel(addr)
}

// ForceDel removes an address from the list of authorized addresses.
// It will not panic if the last member of the set is removed
// WARN: In this case, the ownership of the set is fully removed.
func (s *Set) ForceDel(addr std.Address) error {
if _, removed := s.authorized.Remove(addr.String()); !removed {
return ErrNotInSet
}

return nil
}

// 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
}
94 changes: 94 additions & 0 deletions examples/gno.land/p/demo/access/set_test.gno
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)
}
33 changes: 33 additions & 0 deletions examples/gno.land/p/demo/access/unprivileged.gno
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)
}
104 changes: 104 additions & 0 deletions examples/gno.land/p/demo/access/unprivileged_test.gno
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)
}
Loading
Loading