Authorization framework for gating dendrite endpoints (#39)

* import new versions of the zion contracts

* bootstrap zion authz

* define interface for space manager contract

* instantiate spacemanager interface

* load goerli and localhost

* embed json

* remove zion interface. Use contracts directly

* split user identifiter into address and chain id

* isAllowed in routing.go

* remove permission.go

Co-authored-by: Tak Wai Wong <tak@hntlabs.com>
This commit is contained in:
Tak Wai Wong 2022-09-28 16:11:10 -07:00
parent c9ec018121
commit 04a78694d1
No known key found for this signature in database
GPG key ID: 222E4AF2AA1F467D
18 changed files with 1560 additions and 179 deletions

4
.gitignore vendored
View file

@ -76,4 +76,6 @@ docs/_site
media_store/
# Debug
**/__debug_bin
**/__debug_bin
.env

View file

@ -14,8 +14,6 @@
package authorization
import "github.com/matrix-org/dendrite/setup/config"
type AuthorizationArgs struct {
RoomId string
UserId string
@ -25,11 +23,3 @@ type AuthorizationArgs struct {
type Authorization interface {
IsAllowed(args AuthorizationArgs) (bool, error)
}
func NewClientApiAuthorization(cfg *config.ClientAPI) Authorization {
// Load authorization manager for Zion
//if cfg.PublicKeyAuthentication.Ethereum.EnableAuthz {
//}
return &DefaultAuthorization{}
}

View file

@ -0,0 +1,23 @@
package authorization
import (
"github.com/matrix-org/dendrite/authorization"
"github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/dendrite/zion"
log "github.com/sirupsen/logrus"
)
func NewAuthorization(cfg *config.ClientAPI) authorization.Authorization {
// Load authorization manager for Zion
if cfg.PublicKeyAuthentication.Ethereum.EnableAuthz {
auth, err := zion.NewZionAuthorization()
if err != nil {
log.Errorln("Failed to initialise Zion authorization manager. Using default.", err)
} else {
return auth
}
}
return &authorization.DefaultAuthorization{}
}

View file

@ -27,9 +27,10 @@ import (
"github.com/sirupsen/logrus"
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
"github.com/matrix-org/dendrite/authorization"
authz "github.com/matrix-org/dendrite/authorization"
"github.com/matrix-org/dendrite/clientapi/api"
"github.com/matrix-org/dendrite/clientapi/auth"
clientApiAuthz "github.com/matrix-org/dendrite/clientapi/authorization"
clientutil "github.com/matrix-org/dendrite/clientapi/httputil"
"github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/clientapi/producers"
@ -75,7 +76,7 @@ func Setup(
rateLimits := httputil.NewRateLimits(&cfg.RateLimiting)
userInteractiveAuth := auth.NewUserInteractive(userAPI, userAPI, cfg)
authorization := authorization.NewClientApiAuthorization(cfg)
authorization := clientApiAuthz.NewAuthorization(cfg)
_ = authorization // todo: use this in httputil.MakeAuthAPI
unstableFeatures := map[string]bool{
@ -254,6 +255,15 @@ func Setup(
if err != nil {
return util.ErrorResponse(err)
}
isAllowed, _ := authorization.IsAllowed(authz.AuthorizationArgs{
RoomId: vars["roomIDOrAlias"],
UserId: device.UserID,
Permission: "Zion-Join",
})
logrus.Debugf("/join/%s isAllowed = %t", vars["roomIDOrAlias"], isAllowed)
return JoinRoomByIDOrAlias(
req, device, rsAPI, userAPI, vars["roomIDOrAlias"],
)

3
go.mod
View file

@ -41,7 +41,6 @@ require (
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.13.0
github.com/sirupsen/logrus v1.9.0
github.com/spruceid/siwe-go v0.2.0
github.com/stretchr/testify v1.8.0
github.com/tidwall/gjson v1.14.3
github.com/tidwall/sjson v1.2.5
@ -103,6 +102,7 @@ require (
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/h2non/filetype v1.1.3 // indirect
github.com/joho/godotenv v1.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/juju/errors v1.0.0 // indirect
github.com/klauspost/compress v1.15.11 // indirect
@ -132,6 +132,7 @@ require (
github.com/relvacode/iso8601 v1.1.0 // indirect
github.com/rjeczalik/notify v0.9.1 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/spruceid/siwe-go v0.2.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tklauser/go-sysconf v0.3.5 // indirect

View file

@ -1,65 +0,0 @@
package web3
import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
)
type CreateTransactionSignerArgs struct {
PrivateKey string
ChainId int64
Client *ethclient.Client
GasValue int64 // in wei
GasLimit int64 // in units
}
func CreateTransactionSigner(args CreateTransactionSignerArgs) (*bind.TransactOpts, error) {
privateKey, err := crypto.HexToECDSA(args.PrivateKey)
if err != nil {
return nil, err
}
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
return nil, errors.New("cannot create public key ECDSA")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
nonce, err := args.Client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
return nil, err
}
gasPrice, err := args.Client.SuggestGasPrice((context.Background()))
if err != nil {
return nil, err
}
signer, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(args.ChainId))
if err != nil {
return nil, err
}
signer.Nonce = big.NewInt(int64(nonce))
signer.Value = big.NewInt(args.GasValue)
signer.GasLimit = uint64(args.GasLimit)
signer.GasPrice = gasPrice
fmt.Printf("{ nonce: %d, value: %d, gasLimit: %d, gasPrice: %d }\n",
signer.Nonce,
signer.Value,
signer.GasLimit,
signer.GasPrice,
)
return signer, nil
}

View file

@ -1,14 +0,0 @@
package web3
import (
"github.com/ethereum/go-ethereum/ethclient"
)
func GetEthClient(web3ProviderUrl string) (*ethclient.Client, error) {
client, err := ethclient.Dial(web3ProviderUrl)
if err != nil {
return nil, err
}
return client, nil
}

View file

@ -0,0 +1,22 @@
package zion
import (
"encoding/json"
)
type SpaceManagerContractAddresses struct {
Spacemanager string `json:"spaceManager"`
Usergranted string `json:"usergranted"`
Tokengranted string `json:"tokengranted"`
}
func loadSpaceManagerAddresses(byteValue []byte) (*SpaceManagerContractAddresses, error) {
var addresses SpaceManagerContractAddresses
err := json.Unmarshal(byteValue, &addresses)
if err != nil {
return nil, err
}
return &addresses, nil
}

View file

@ -0,0 +1 @@
{"councilnft": "0xae599a7c5f2cad3acd773e1931045384d70cb4e6"}

View file

@ -0,0 +1 @@
{"spacemanager": "0x4b924167de1cd8353a508b31d6607ab571e762ec","usergranted": "0x07f32c3c668e84ac8b91c6c9e4703b4e655f9efc","tokengranted": "0x3de349ff0fd4235f85035d970e88156496208838"}

View file

@ -0,0 +1 @@
{"councilnft": "0x2279b7a0a67db372996a5fab50d91eaa73d2ebe6"}

View file

@ -0,0 +1 @@
{"spacemanager": "0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0","usergranted": "0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9","tokengranted": "0xdc64a140aa3e981100a9beca4e685f962f0cf6c9"}

38
zion/user_identifier.go Normal file
View file

@ -0,0 +1,38 @@
package zion
import (
"regexp"
"strconv"
"github.com/ethereum/go-ethereum/common"
)
var regexpMatrixId = regexp.MustCompile(`^@eip155=3a(?P<ChainId>[0-9]+)=3a(?P<LocalPart>0x[0-9a-fA-F]+):(?P<HomeServer>.*)$`)
var chainIdIndex = regexpMatrixId.SubexpIndex("ChainId")
var localPartIndex = regexpMatrixId.SubexpIndex("LocalPart")
//var homeServerIndex = regexpMatrixId.SubexpIndex("HomeServer")
type UserIdentifier struct {
accountAddress common.Address
chainId int
}
func CreateUserIdentifier(matrixUserId string) UserIdentifier {
matches := regexpMatrixId.FindStringSubmatch(matrixUserId)
accountAddress := ""
chainId := -1
if chainIdIndex < len(matches) {
chainId, _ = strconv.Atoi(matches[chainIdIndex])
}
if localPartIndex < len(matches) {
accountAddress = matches[localPartIndex]
}
return UserIdentifier{
accountAddress: common.HexToAddress(accountAddress),
chainId: chainId,
}
}

14
zion/web3_util.go Normal file
View file

@ -0,0 +1,14 @@
package zion
import (
"github.com/ethereum/go-ethereum/ethclient"
)
func GetEthClient(networkUrl string) (*ethclient.Client, error) {
client, err := ethclient.Dial(networkUrl)
if err != nil {
return nil, err
}
return client, nil
}

161
zion/zion_authorization.go Normal file
View file

@ -0,0 +1,161 @@
package zion
import (
_ "embed"
"math/big"
"os"
"github.com/ethereum/go-ethereum/common"
"github.com/joho/godotenv"
"github.com/matrix-org/dendrite/authorization"
log "github.com/sirupsen/logrus"
)
const (
localhostEndpointUrl = "LOCALHOST_ENDPOINT" // .env
goerliEndpointUrl = "GOERLI_ENDPOINT" // .env
)
//go:embed contracts/localhost/addresses/space-manager.json
var localhostJson []byte
//go:embed contracts/goerli/addresses/space-manager.json
var goerliJson []byte
type ZionAuthorization struct {
spaceManagerLocalhost *ZionSpaceManagerLocalhost
spaceManagerGoerli *ZionSpaceManagerGoerli
}
func NewZionAuthorization() (authorization.Authorization, error) {
err := godotenv.Load(".env")
if err != nil {
log.Errorln("error loading .env file", err)
}
var auth ZionAuthorization
localhost, err := newZionSpaceManagerLocalhost(os.Getenv(localhostEndpointUrl))
if err != nil {
log.Errorln("error instantiating ZionSpaceManagerLocalhost", err)
}
auth.spaceManagerLocalhost = localhost
goerli, err := newZionSpaceManagerGoerli(os.Getenv(goerliEndpointUrl))
if err != nil {
log.Errorln("error instantiating ZionSpaceManagerGoerli", err)
}
auth.spaceManagerGoerli = goerli
return &auth, nil
}
func (za *ZionAuthorization) IsAllowed(args authorization.AuthorizationArgs) (bool, error) {
userIdentifier := CreateUserIdentifier(args.UserId)
permission := DataTypesPermission{
Name: args.Permission,
}
switch userIdentifier.chainId {
case 1337, 31337:
return za.IsAllowedLocalhost(args.RoomId, userIdentifier.accountAddress, permission)
case 5:
return za.IsAllowedGoerli(args.RoomId, userIdentifier.accountAddress, permission)
default:
log.Errorf("Unsupported chain id: %d\n", userIdentifier.chainId)
}
return false, nil
}
func (za *ZionAuthorization) IsAllowedLocalhost(roomId string, user common.Address, permission DataTypesPermission) (bool, error) {
if za.spaceManagerLocalhost != nil {
spaceId, err := za.spaceManagerLocalhost.GetSpaceIdByNetworkId(nil, roomId)
if err != nil {
return false, err
}
isEntitled, err := za.spaceManagerLocalhost.IsEntitled(
nil,
spaceId,
big.NewInt(0),
user,
permission,
)
if err != nil {
return false, err
}
return isEntitled, nil
}
return false, nil
}
func (za *ZionAuthorization) IsAllowedGoerli(roomId string, user common.Address, permission DataTypesPermission) (bool, error) {
if za.spaceManagerGoerli != nil {
spaceId, err := za.spaceManagerGoerli.GetSpaceIdByNetworkId(nil, roomId)
if err != nil {
return false, err
}
isEntitled, err := za.spaceManagerGoerli.IsEntitled(
nil,
spaceId,
big.NewInt(0),
user,
permission,
)
if err != nil {
return false, err
}
return isEntitled, nil
}
return false, nil
}
func newZionSpaceManagerLocalhost(endpointUrl string) (*ZionSpaceManagerLocalhost, error) {
addresses, err := loadSpaceManagerAddresses(localhostJson)
if err != nil {
return nil, err
}
address := common.HexToAddress(addresses.Spacemanager)
client, err := GetEthClient(endpointUrl)
if err != nil {
return nil, err
}
spaceManager, err := NewZionSpaceManagerLocalhost(address, client)
if err != nil {
return nil, err
}
return spaceManager, nil
}
func newZionSpaceManagerGoerli(endpointUrl string) (*ZionSpaceManagerGoerli, error) {
addresses, err := loadSpaceManagerAddresses(goerliJson)
if err != nil {
return nil, err
}
address := common.HexToAddress((addresses.Spacemanager))
client, err := GetEthClient(endpointUrl)
if err != nil {
return nil, err
}
spaceManager, err := NewZionSpaceManagerGoerli(address, client)
if err != nil {
return nil, err
}
return spaceManager, nil
}

38
zion/zion_data_types.go Normal file
View file

@ -0,0 +1,38 @@
package zion
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
)
// DataTypesCreateSpaceData is an auto generated low-level Go binding around an user-defined struct.
type DataTypesCreateSpaceData struct {
SpaceName string
NetworkId string
}
// DataTypesCreateSpaceTokenEntitlementData is an auto generated low-level Go binding around an user-defined struct.
type DataTypesCreateSpaceTokenEntitlementData struct {
EntitlementModuleAddress common.Address
TokenAddress common.Address
Quantity *big.Int
Description string
EntitlementTypes []uint8
}
// DataTypesEntitlementModuleInfo is an auto generated low-level Go binding around an user-defined struct.
type DataTypesEntitlementModuleInfo struct {
EntitlementAddress common.Address
EntitlementName string
EntitlementDescription string
}
// DataTypesSpaceInfo is an auto generated low-level Go binding around an user-defined struct.
type DataTypesSpaceInfo struct {
SpaceId *big.Int
CreatedAt *big.Int
Name string
Creator common.Address
Owner common.Address
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long