Add HighestRole & ordered roles to permission response
This commit adds HighestRole & a list of permissions in order of importance to the CheckPermissionToJoinResponse. This simplify client code so that it doesn't need to be calculated on the client.
This commit is contained in:
parent
8fa31df498
commit
daef5c56e2
|
@ -0,0 +1,151 @@
|
|||
package communities
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sort"
|
||||
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
)
|
||||
|
||||
type CheckPermissionsResponse struct {
|
||||
Satisfied bool `json:"satisfied"`
|
||||
Permissions map[string]*PermissionTokenCriteriaResult `json:"permissions"`
|
||||
ValidCombinations []*AccountChainIDsCombination `json:"validCombinations"`
|
||||
}
|
||||
|
||||
type CheckPermissionToJoinResponse = CheckPermissionsResponse
|
||||
|
||||
type HighestRoleResponse struct {
|
||||
Role protobuf.CommunityTokenPermission_Type `json:"type"`
|
||||
Satisfied bool `json:"satisfied"`
|
||||
Criteria []*PermissionTokenCriteriaResult `json:"criteria"`
|
||||
}
|
||||
|
||||
var joiningRoleOrders = map[protobuf.CommunityTokenPermission_Type]int{
|
||||
protobuf.CommunityTokenPermission_BECOME_MEMBER: 1,
|
||||
protobuf.CommunityTokenPermission_BECOME_ADMIN: 2,
|
||||
protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER: 3,
|
||||
protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER: 4,
|
||||
}
|
||||
|
||||
type ByRoleDesc []*HighestRoleResponse
|
||||
|
||||
func (a ByRoleDesc) Len() int { return len(a) }
|
||||
func (a ByRoleDesc) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByRoleDesc) Less(i, j int) bool {
|
||||
return joiningRoleOrders[a[i].Role] > joiningRoleOrders[a[j].Role]
|
||||
}
|
||||
|
||||
type rolesAndHighestRole struct {
|
||||
Roles []*HighestRoleResponse
|
||||
HighestRole *HighestRoleResponse
|
||||
}
|
||||
|
||||
func calculateRolesAndHighestRole(permissions map[string]*PermissionTokenCriteriaResult) *rolesAndHighestRole {
|
||||
item := &rolesAndHighestRole{}
|
||||
byRoleMap := make(map[protobuf.CommunityTokenPermission_Type]*HighestRoleResponse)
|
||||
for _, p := range permissions {
|
||||
if joiningRoleOrders[p.Role] == 0 {
|
||||
continue
|
||||
}
|
||||
if byRoleMap[p.Role] == nil {
|
||||
byRoleMap[p.Role] = &HighestRoleResponse{
|
||||
Role: p.Role,
|
||||
}
|
||||
}
|
||||
|
||||
satisfied := true
|
||||
for _, tr := range p.TokenRequirements {
|
||||
if !tr.Satisfied {
|
||||
satisfied = false
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if satisfied {
|
||||
byRoleMap[p.Role].Satisfied = true
|
||||
// we prepend
|
||||
byRoleMap[p.Role].Criteria = append([]*PermissionTokenCriteriaResult{p}, byRoleMap[p.Role].Criteria...)
|
||||
} else {
|
||||
// we append then
|
||||
byRoleMap[p.Role].Criteria = append(byRoleMap[p.Role].Criteria, p)
|
||||
}
|
||||
}
|
||||
if byRoleMap[protobuf.CommunityTokenPermission_BECOME_MEMBER] == nil {
|
||||
byRoleMap[protobuf.CommunityTokenPermission_BECOME_MEMBER] = &HighestRoleResponse{Satisfied: true, Role: protobuf.CommunityTokenPermission_BECOME_MEMBER}
|
||||
}
|
||||
for _, p := range byRoleMap {
|
||||
item.Roles = append(item.Roles, p)
|
||||
}
|
||||
|
||||
sort.Sort(ByRoleDesc(item.Roles))
|
||||
for _, r := range item.Roles {
|
||||
if r.Satisfied {
|
||||
item.HighestRole = r
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
func (c *CheckPermissionsResponse) MarshalJSON() ([]byte, error) {
|
||||
type CheckPermissionsTypeAlias struct {
|
||||
Satisfied bool `json:"satisfied"`
|
||||
Permissions map[string]*PermissionTokenCriteriaResult `json:"permissions"`
|
||||
ValidCombinations []*AccountChainIDsCombination `json:"validCombinations"`
|
||||
Roles []*HighestRoleResponse `json:"roles"`
|
||||
HighestRole *HighestRoleResponse `json:"highestRole"`
|
||||
}
|
||||
c.calculateSatisfied()
|
||||
item := &CheckPermissionsTypeAlias{
|
||||
Satisfied: c.Satisfied,
|
||||
Permissions: c.Permissions,
|
||||
ValidCombinations: c.ValidCombinations,
|
||||
}
|
||||
rolesAndHighestRole := calculateRolesAndHighestRole(c.Permissions)
|
||||
|
||||
item.Roles = rolesAndHighestRole.Roles
|
||||
item.HighestRole = rolesAndHighestRole.HighestRole
|
||||
return json.Marshal(item)
|
||||
}
|
||||
|
||||
type TokenRequirementResponse struct {
|
||||
Satisfied bool `json:"satisfied"`
|
||||
TokenCriteria *protobuf.TokenCriteria `json:"criteria"`
|
||||
}
|
||||
|
||||
type PermissionTokenCriteriaResult struct {
|
||||
Role protobuf.CommunityTokenPermission_Type `json:"roles"`
|
||||
TokenRequirements []TokenRequirementResponse `json:"tokenRequirement"`
|
||||
Criteria []bool `json:"criteria"`
|
||||
}
|
||||
|
||||
type AccountChainIDsCombination struct {
|
||||
Address gethcommon.Address `json:"address"`
|
||||
ChainIDs []uint64 `json:"chainIds"`
|
||||
}
|
||||
|
||||
func (c *CheckPermissionsResponse) calculateSatisfied() {
|
||||
if len(c.Permissions) == 0 {
|
||||
c.Satisfied = true
|
||||
return
|
||||
}
|
||||
|
||||
c.Satisfied = false
|
||||
for _, p := range c.Permissions {
|
||||
satisfied := true
|
||||
for _, criteria := range p.Criteria {
|
||||
if !criteria {
|
||||
satisfied = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if satisfied {
|
||||
c.Satisfied = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package communities
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
)
|
||||
|
||||
func TestCalculateRolesAndHighestRole(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
permissions map[string]*PermissionTokenCriteriaResult
|
||||
expectedRolesOrder []protobuf.CommunityTokenPermission_Type
|
||||
expectedHighestRole protobuf.CommunityTokenPermission_Type
|
||||
}{
|
||||
{
|
||||
name: "Basic scenario with multiple permissions",
|
||||
permissions: map[string]*PermissionTokenCriteriaResult{
|
||||
"1": {
|
||||
Role: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
||||
TokenRequirements: []TokenRequirementResponse{
|
||||
{Satisfied: true},
|
||||
},
|
||||
},
|
||||
"2": {
|
||||
Role: protobuf.CommunityTokenPermission_BECOME_ADMIN,
|
||||
TokenRequirements: []TokenRequirementResponse{
|
||||
{Satisfied: true},
|
||||
},
|
||||
},
|
||||
"3": {
|
||||
Role: protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER,
|
||||
TokenRequirements: []TokenRequirementResponse{
|
||||
{Satisfied: false},
|
||||
},
|
||||
},
|
||||
"4": {
|
||||
Role: protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER,
|
||||
TokenRequirements: []TokenRequirementResponse{
|
||||
{Satisfied: false},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRolesOrder: []protobuf.CommunityTokenPermission_Type{protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER, protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER, protobuf.CommunityTokenPermission_BECOME_ADMIN, protobuf.CommunityTokenPermission_BECOME_MEMBER},
|
||||
expectedHighestRole: protobuf.CommunityTokenPermission_BECOME_ADMIN,
|
||||
},
|
||||
{
|
||||
name: "No member permission created",
|
||||
permissions: map[string]*PermissionTokenCriteriaResult{
|
||||
"2": {
|
||||
Role: protobuf.CommunityTokenPermission_BECOME_ADMIN,
|
||||
TokenRequirements: []TokenRequirementResponse{
|
||||
{Satisfied: false},
|
||||
},
|
||||
},
|
||||
"3": {
|
||||
Role: protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER,
|
||||
TokenRequirements: []TokenRequirementResponse{
|
||||
{Satisfied: false},
|
||||
},
|
||||
},
|
||||
"4": {
|
||||
Role: protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER,
|
||||
TokenRequirements: []TokenRequirementResponse{
|
||||
{Satisfied: false},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRolesOrder: []protobuf.CommunityTokenPermission_Type{protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER, protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER, protobuf.CommunityTokenPermission_BECOME_ADMIN, protobuf.CommunityTokenPermission_BECOME_MEMBER},
|
||||
expectedHighestRole: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
||||
},
|
||||
{
|
||||
name: "no permission satisfied",
|
||||
permissions: map[string]*PermissionTokenCriteriaResult{
|
||||
"1": {
|
||||
Role: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
||||
TokenRequirements: []TokenRequirementResponse{
|
||||
{Satisfied: false},
|
||||
},
|
||||
},
|
||||
"2": {
|
||||
Role: protobuf.CommunityTokenPermission_BECOME_ADMIN,
|
||||
TokenRequirements: []TokenRequirementResponse{
|
||||
{Satisfied: false},
|
||||
},
|
||||
},
|
||||
"3": {
|
||||
Role: protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER,
|
||||
TokenRequirements: []TokenRequirementResponse{
|
||||
{Satisfied: false},
|
||||
},
|
||||
},
|
||||
"4": {
|
||||
Role: protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER,
|
||||
TokenRequirements: []TokenRequirementResponse{
|
||||
{Satisfied: false},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedRolesOrder: []protobuf.CommunityTokenPermission_Type{protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER, protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER, protobuf.CommunityTokenPermission_BECOME_ADMIN, protobuf.CommunityTokenPermission_BECOME_MEMBER},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := calculateRolesAndHighestRole(tc.permissions)
|
||||
var actualOrder []protobuf.CommunityTokenPermission_Type
|
||||
for _, r := range result.Roles {
|
||||
actualOrder = append(actualOrder, r.Role)
|
||||
}
|
||||
if tc.expectedHighestRole == 0 {
|
||||
assert.Nil(t, result.HighestRole)
|
||||
} else {
|
||||
assert.Equal(t, tc.expectedHighestRole, result.HighestRole.Role, "Highest role is not calculated as expected")
|
||||
}
|
||||
assert.Equal(t, tc.expectedRolesOrder, actualOrder, "Roles are not calculated as expected")
|
||||
})
|
||||
}
|
||||
}
|
|
@ -2602,45 +2602,6 @@ func (m *Manager) HandleCommunityEditSharedAddresses(signer *ecdsa.PublicKey, re
|
|||
return nil
|
||||
}
|
||||
|
||||
type CheckPermissionsResponse struct {
|
||||
Satisfied bool `json:"satisfied"`
|
||||
Permissions map[string]*PermissionTokenCriteriaResult `json:"permissions"`
|
||||
ValidCombinations []*AccountChainIDsCombination `json:"validCombinations"`
|
||||
}
|
||||
|
||||
type CheckPermissionToJoinResponse = CheckPermissionsResponse
|
||||
|
||||
type PermissionTokenCriteriaResult struct {
|
||||
Criteria []bool `json:"criteria"`
|
||||
}
|
||||
|
||||
type AccountChainIDsCombination struct {
|
||||
Address gethcommon.Address `json:"address"`
|
||||
ChainIDs []uint64 `json:"chainIds"`
|
||||
}
|
||||
|
||||
func (c *CheckPermissionsResponse) calculateSatisfied() {
|
||||
if len(c.Permissions) == 0 {
|
||||
c.Satisfied = true
|
||||
return
|
||||
}
|
||||
|
||||
c.Satisfied = false
|
||||
for _, p := range c.Permissions {
|
||||
satisfied := true
|
||||
for _, criteria := range p.Criteria {
|
||||
if !criteria {
|
||||
satisfied = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if satisfied {
|
||||
c.Satisfied = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func calculateChainIDsSet(accountsAndChainIDs []*AccountChainIDsCombination, requirementsChainIDs map[uint64]bool) []uint64 {
|
||||
|
||||
revealedAccountsChainIDs := make([]uint64, 0)
|
||||
|
|
|
@ -263,7 +263,7 @@ func (p *DefaultPermissionChecker) CheckPermissions(permissions []*CommunityToke
|
|||
for _, tokenPermission := range permissions {
|
||||
|
||||
permissionRequirementsMet := true
|
||||
response.Permissions[tokenPermission.Id] = &PermissionTokenCriteriaResult{}
|
||||
response.Permissions[tokenPermission.Id] = &PermissionTokenCriteriaResult{Role: tokenPermission.Type}
|
||||
|
||||
// There can be multiple token requirements per permission.
|
||||
// If only one is not met, the entire permission is marked
|
||||
|
@ -271,9 +271,12 @@ func (p *DefaultPermissionChecker) CheckPermissions(permissions []*CommunityToke
|
|||
for _, tokenRequirement := range tokenPermission.TokenCriteria {
|
||||
|
||||
tokenRequirementMet := false
|
||||
tokenRequirementResponse := TokenRequirementResponse{TokenCriteria: tokenRequirement}
|
||||
|
||||
if tokenRequirement.Type == protobuf.CommunityTokenType_ERC721 {
|
||||
if len(ownedERC721Tokens) == 0 {
|
||||
|
||||
response.Permissions[tokenPermission.Id].TokenRequirements = append(response.Permissions[tokenPermission.Id].TokenRequirements, tokenRequirementResponse)
|
||||
response.Permissions[tokenPermission.Id].Criteria = append(response.Permissions[tokenPermission.Id].Criteria, false)
|
||||
continue
|
||||
}
|
||||
|
@ -321,6 +324,7 @@ func (p *DefaultPermissionChecker) CheckPermissions(permissions []*CommunityToke
|
|||
}
|
||||
} else if tokenRequirement.Type == protobuf.CommunityTokenType_ERC20 {
|
||||
if len(ownedERC20TokenBalances) == 0 {
|
||||
response.Permissions[tokenPermission.Id].TokenRequirements = append(response.Permissions[tokenPermission.Id].TokenRequirements, tokenRequirementResponse)
|
||||
response.Permissions[tokenPermission.Id].Criteria = append(response.Permissions[tokenPermission.Id].Criteria, false)
|
||||
continue
|
||||
}
|
||||
|
@ -406,6 +410,9 @@ func (p *DefaultPermissionChecker) CheckPermissions(permissions []*CommunityToke
|
|||
if !tokenRequirementMet {
|
||||
permissionRequirementsMet = false
|
||||
}
|
||||
|
||||
tokenRequirementResponse.Satisfied = tokenRequirementMet
|
||||
response.Permissions[tokenPermission.Id].TokenRequirements = append(response.Permissions[tokenPermission.Id].TokenRequirements, tokenRequirementResponse)
|
||||
response.Permissions[tokenPermission.Id].Criteria = append(response.Permissions[tokenPermission.Id].Criteria, tokenRequirementMet)
|
||||
}
|
||||
// multiple permissions are treated as logical OR, meaning
|
||||
|
|
Loading…
Reference in New Issue