From 69d3995a091942afe61e5ab425ed9ab0bd277377 Mon Sep 17 00:00:00 2001 From: Qi Xiao Date: Thu, 27 Dec 2018 18:29:53 +0800 Subject: [PATCH 01/10] Support query pattern regulation --- blockproducer/metastate.go | 19 ++--- blockproducer/metastate_test.go | 39 +++++---- client/driver.go | 2 +- client/helper_test.go | 2 +- cmd/cql-minerd/integration_test.go | 3 +- cmd/cql-observer/observation_test.go | 8 +- cmd/cql/main.go | 4 +- types/account.go | 95 ++++++++++++++++----- types/account_gen.go | 51 ++++++++++- types/account_gen_test.go | 37 ++++++++ types/updatepermission.go | 2 +- types/updatepermission_gen.go | 18 +++- types/xxx_test.go | 20 +---- worker/chainbusservice_test.go | 4 +- worker/dbms.go | 73 +++++++++++----- worker/dbms_test.go | 123 ++++++++++++++++++++++++--- worker/helper_test.go | 8 +- 17 files changed, 383 insertions(+), 125 deletions(-) diff --git a/blockproducer/metastate.go b/blockproducer/metastate.go index 0b2c290e6..0390e832e 100644 --- a/blockproducer/metastate.go +++ b/blockproducer/metastate.go @@ -458,7 +458,7 @@ func (s *metaState) createSQLChain(addr proto.AccountAddress, id proto.DatabaseI Users: []*types.SQLChainUser{ { Address: addr, - Permission: types.Admin, + Permission: types.UserPermissionFromRole(types.Admin), }, }, } @@ -466,7 +466,7 @@ func (s *metaState) createSQLChain(addr proto.AccountAddress, id proto.DatabaseI } func (s *metaState) addSQLChainUser( - k proto.DatabaseID, addr proto.AccountAddress, perm types.UserPermission) (_ error, + k proto.DatabaseID, addr proto.AccountAddress, perm *types.UserPermission) (_ error, ) { var ( src, dst *types.SQLChainProfile @@ -515,8 +515,7 @@ func (s *metaState) deleteSQLChainUser(k proto.DatabaseID, addr proto.AccountAdd } func (s *metaState) alterSQLChainUser( - k proto.DatabaseID, addr proto.AccountAddress, perm types.UserPermission) (_ error, -) { + k proto.DatabaseID, addr proto.AccountAddress, perm *types.UserPermission) (_ error) { var ( src, dst *types.SQLChainProfile ok bool @@ -703,7 +702,7 @@ func (s *metaState) matchProvidersWithUser(tx *types.CreateDatabase) (err error) users := make([]*types.SQLChainUser, 1) users[0] = &types.SQLChainUser{ Address: sender, - Permission: types.Admin, + Permission: types.UserPermissionFromRole(types.Admin), Status: types.Normal, Deposit: minAdvancePayment, AdvancePayment: tx.AdvancePayment, @@ -886,7 +885,7 @@ func (s *metaState) updatePermission(tx *types.UpdatePermission) (err error) { }).WithError(ErrDatabaseNotFound).Error("unexpected error in updatePermission") return ErrDatabaseNotFound } - if tx.Permission >= types.NumberOfUserPermission { + if !tx.Permission.IsValid() { log.WithFields(log.Fields{ "permission": tx.Permission, "dbID": tx.TargetSQLChain.DatabaseID(), @@ -899,8 +898,8 @@ func (s *metaState) updatePermission(tx *types.UpdatePermission) (err error) { numOfAdmin := 0 targetUserIndex := -1 for i, u := range so.Users { - isAdmin = isAdmin || (sender == u.Address && u.Permission == types.Admin) - if u.Permission == types.Admin { + isAdmin = isAdmin || (sender == u.Address && u.Permission.HasAdminPermission()) + if u.Permission.HasAdminPermission() { numOfAdmin++ } if tx.TargetUser == u.Address { @@ -917,7 +916,7 @@ func (s *metaState) updatePermission(tx *types.UpdatePermission) (err error) { } // return error if number of Admin <= 1 and Admin want to revoke permission of itself - if numOfAdmin <= 1 && tx.TargetUser == sender && tx.Permission != types.Admin { + if numOfAdmin <= 1 && tx.TargetUser == sender && !tx.Permission.HasAdminPermission() { err = ErrNoAdminLeft log.WithFields(log.Fields{ "sender": sender, @@ -955,7 +954,7 @@ func (s *metaState) updateKeys(tx *types.IssueKeys) (err error) { // check sender's permission isAdmin := false for _, user := range so.Users { - if sender == user.Address && user.Permission == types.Admin { + if sender == user.Address && user.Permission.HasAdminPermission() { isAdmin = true break } diff --git a/blockproducer/metastate_test.go b/blockproducer/metastate_test.go index f6bca9ac2..aedd69e87 100644 --- a/blockproducer/metastate_test.go +++ b/blockproducer/metastate_test.go @@ -106,11 +106,11 @@ func TestMetaState(t *testing.T) { Convey("The metaState should failed to operate SQLChain for unknown user", func() { err = ms.createSQLChain(addr1, dbID1) So(err, ShouldEqual, ErrAccountNotFound) - err = ms.addSQLChainUser(dbID1, addr1, types.Admin) + err = ms.addSQLChainUser(dbID1, addr1, types.UserPermissionFromRole(types.Admin)) So(err, ShouldEqual, ErrDatabaseNotFound) err = ms.deleteSQLChainUser(dbID1, addr1) So(err, ShouldEqual, ErrDatabaseNotFound) - err = ms.alterSQLChainUser(dbID1, addr1, types.Write) + err = ms.alterSQLChainUser(dbID1, addr1, types.UserPermissionFromRole(types.Write)) So(err, ShouldEqual, ErrDatabaseNotFound) }) Convey("When new account and database objects are stored", func() { @@ -170,9 +170,9 @@ func TestMetaState(t *testing.T) { So(err, ShouldEqual, ErrDatabaseExists) }) Convey("When new SQLChain users are added", func() { - err = ms.addSQLChainUser(dbID3, addr2, types.Write) + err = ms.addSQLChainUser(dbID3, addr2, types.UserPermissionFromRole(types.Write)) So(err, ShouldBeNil) - err = ms.addSQLChainUser(dbID3, addr2, types.Write) + err = ms.addSQLChainUser(dbID3, addr2, types.UserPermissionFromRole(types.Write)) So(err, ShouldEqual, ErrDatabaseUserExists) Convey("The metaState object should be ok to delete user", func() { err = ms.deleteSQLChainUser(dbID3, addr2) @@ -181,9 +181,9 @@ func TestMetaState(t *testing.T) { So(err, ShouldBeNil) }) Convey("The metaState object should be ok to alter user", func() { - err = ms.alterSQLChainUser(dbID3, addr2, types.Read) + err = ms.alterSQLChainUser(dbID3, addr2, types.UserPermissionFromRole(types.Read)) So(err, ShouldBeNil) - err = ms.alterSQLChainUser(dbID3, addr2, types.Write) + err = ms.alterSQLChainUser(dbID3, addr2, types.UserPermissionFromRole(types.Write)) So(err, ShouldBeNil) }) Convey("When metaState change is committed", func() { @@ -204,9 +204,9 @@ func TestMetaState(t *testing.T) { So(err, ShouldBeNil) }) Convey("The metaState object should be ok to alter user", func() { - err = ms.alterSQLChainUser(dbID3, addr2, types.Read) + err = ms.alterSQLChainUser(dbID3, addr2, types.UserPermissionFromRole(types.Read)) So(err, ShouldBeNil) - err = ms.alterSQLChainUser(dbID3, addr2, types.Write) + err = ms.alterSQLChainUser(dbID3, addr2, types.UserPermissionFromRole(types.Write)) So(err, ShouldBeNil) }) }) @@ -214,9 +214,9 @@ func TestMetaState(t *testing.T) { Convey("When metaState change is committed", func() { ms.commit() Convey("The metaState object should be ok to add users for database", func() { - err = ms.addSQLChainUser(dbID3, addr2, types.Write) + err = ms.addSQLChainUser(dbID3, addr2, types.UserPermissionFromRole(types.Write)) So(err, ShouldBeNil) - err = ms.addSQLChainUser(dbID3, addr2, types.Write) + err = ms.addSQLChainUser(dbID3, addr2, types.UserPermissionFromRole(types.Write)) So(err, ShouldEqual, ErrDatabaseUserExists) }) Convey("The metaState object should report database exists", func() { @@ -992,7 +992,7 @@ func TestMetaState(t *testing.T) { UpdatePermissionHeader: types.UpdatePermissionHeader{ TargetSQLChain: addr1, TargetUser: addr3, - Permission: types.Read, + Permission: types.UserPermissionFromRole(types.Read), Nonce: cd1.Nonce + 1, }, } @@ -1000,7 +1000,7 @@ func TestMetaState(t *testing.T) { So(err, ShouldBeNil) err = ms.apply(&up) So(errors.Cause(err), ShouldEqual, ErrDatabaseNotFound) - up.Permission = 4 + up.Permission = types.UserPermissionFromRole(types.NumberOfUserPermission) up.TargetSQLChain = dbAccount err = up.Sign(privKey1) So(err, ShouldBeNil) @@ -1009,7 +1009,7 @@ func TestMetaState(t *testing.T) { // test permission update // addr1(admin) update addr3 as admin up.TargetUser = addr3 - up.Permission = types.Admin + up.Permission = types.UserPermissionFromRole(types.Admin) err = up.Sign(privKey1) So(err, ShouldBeNil) err = ms.apply(&up) @@ -1018,7 +1018,7 @@ func TestMetaState(t *testing.T) { // addr3(admin) update addr4 as read up.TargetUser = addr4 up.Nonce = cd2.Nonce - up.Permission = types.Read + up.Permission = types.UserPermissionFromRole(types.Read) err = up.Sign(privKey3) So(err, ShouldBeNil) err = ms.apply(&up) @@ -1034,7 +1034,7 @@ func TestMetaState(t *testing.T) { ms.commit() // addr3(admin) update addr3(admin) as read fail up.TargetUser = addr3 - up.Permission = types.Read + up.Permission = types.UserPermissionFromRole(types.Read) up.Nonce = up.Nonce + 1 err = up.Sign(privKey3) So(err, ShouldBeNil) @@ -1050,15 +1050,18 @@ func TestMetaState(t *testing.T) { co, loaded = ms.loadSQLChainObject(dbID) for _, user := range co.Users { if user.Address == addr1 { - So(user.Permission, ShouldEqual, types.Read) + So(user.Permission, ShouldNotBeNil) + So(user.Permission.Role, ShouldEqual, types.Read) continue } if user.Address == addr3 { - So(user.Permission, ShouldEqual, types.Admin) + So(user.Permission, ShouldNotBeNil) + So(user.Permission.Role, ShouldEqual, types.Admin) continue } if user.Address == addr4 { - So(user.Permission, ShouldEqual, types.Read) + So(user.Permission, ShouldNotBeNil) + So(user.Permission.Role, ShouldEqual, types.Read) continue } } diff --git a/client/driver.go b/client/driver.go index ece39b3a0..0f0ca1eb5 100644 --- a/client/driver.go +++ b/client/driver.go @@ -279,7 +279,7 @@ func GetTokenBalance(tt types.TokenType) (balance uint64, err error) { // UpdatePermission sends UpdatePermission transaction to chain. func UpdatePermission(targetUser proto.AccountAddress, - targetChain proto.AccountAddress, perm types.UserPermission) (txHash hash.Hash, err error) { + targetChain proto.AccountAddress, perm *types.UserPermission) (txHash hash.Hash, err error) { if atomic.LoadUint32(&driverInitialized) == 0 { err = ErrNotInitialized return diff --git a/client/helper_test.go b/client/helper_test.go index 7ff55c20b..e72a71acb 100644 --- a/client/helper_test.go +++ b/client/helper_test.go @@ -179,7 +179,7 @@ func startTestService() (stopTestService func(), tempDir string, err error) { return } permStat := &types.PermStat{ - Permission: types.Admin, + Permission: types.UserPermissionFromRole(types.Admin), Status: types.Normal, } err = dbms.UpdatePermission(dbID, proto.AccountAddress(addr), permStat) diff --git a/cmd/cql-minerd/integration_test.go b/cmd/cql-minerd/integration_test.go index b8984630c..8d7351f82 100644 --- a/cmd/cql-minerd/integration_test.go +++ b/cmd/cql-minerd/integration_test.go @@ -439,7 +439,8 @@ func TestFullProcess(t *testing.T) { } permStat, ok := usersMap[clientAddr] So(ok, ShouldBeTrue) - So(permStat.Permission, ShouldEqual, types.Admin) + So(permStat.Permission, ShouldNotBeNil) + So(permStat.Permission.Role, ShouldEqual, types.Admin) So(permStat.Status, ShouldEqual, types.Normal) _, err = db.Exec("CREATE TABLE test (test int)") diff --git a/cmd/cql-observer/observation_test.go b/cmd/cql-observer/observation_test.go index 38791dfbd..81e1f35e3 100644 --- a/cmd/cql-observer/observation_test.go +++ b/cmd/cql-observer/observation_test.go @@ -322,7 +322,7 @@ func TestFullProcess(t *testing.T) { up := types.NewUpdatePermission(&types.UpdatePermissionHeader{ TargetSQLChain: dbAddr, TargetUser: obAddr, - Permission: types.Read, + Permission: types.UserPermissionFromRole(types.Read), Nonce: nonce, }) err = up.Sign(cliPriv) @@ -344,7 +344,7 @@ func TestFullProcess(t *testing.T) { "stat": user.Status, }).Debug("checkFunc 1") if user.Address == obAddr { - return user.Permission.CheckRead() + return user.Permission.HasReadPermission() } } return false @@ -629,7 +629,7 @@ func TestFullProcess(t *testing.T) { up = types.NewUpdatePermission(&types.UpdatePermissionHeader{ TargetSQLChain: dbAddr2, TargetUser: obAddr, - Permission: types.Read, + Permission: types.UserPermissionFromRole(types.Read), Nonce: nonce, }) err = up.Sign(cliPriv) @@ -646,7 +646,7 @@ func TestFullProcess(t *testing.T) { err = waitProfileChecking(ctx4, 3*time.Second, proto.DatabaseID(dbID2), func(profile *types.SQLChainProfile) bool { for _, user := range profile.Users { if user.Address == obAddr { - return user.Permission.CheckRead() + return user.Permission.HasReadPermission() } } return false diff --git a/cmd/cql/main.go b/cmd/cql/main.go index cf820e163..547b2ba58 100644 --- a/cmd/cql/main.go +++ b/cmd/cql/main.go @@ -372,13 +372,13 @@ func main() { var p types.UserPermission p.FromString(perm.Perm) - if p > types.NumberOfUserPermission { + if p.Role > types.NumberOfUserPermission { log.WithError(err).Errorf("update permission failed: invalid permission description") os.Exit(-1) return } - txHash, err := client.UpdatePermission(perm.TargetUser, perm.TargetChain, p) + txHash, err := client.UpdatePermission(perm.TargetUser, perm.TargetChain, &p) if err != nil { log.WithError(err).Error("update permission failed") diff --git a/types/account.go b/types/account.go index d6a80efb7..299041ba5 100644 --- a/types/account.go +++ b/types/account.go @@ -36,12 +36,21 @@ const ( NumberOfRoles ) +// UserPermissionRole defines role of user permission including admin/write/read. +type UserPermissionRole int32 + // UserPermission defines permissions of a SQLChain user. -type UserPermission int32 +type UserPermission struct { + // User role to access database. + Role UserPermissionRole + // SQL pattern regulations for user queries + // only a fully matched (case-sensitive) sql query is permitted to execute. + Patterns []string +} const ( // Void defines the initial permission. - Void UserPermission = iota + Void UserPermissionRole = iota // Admin defines the admin user permission. Admin // Write defines the writer user permission. @@ -52,39 +61,83 @@ const ( NumberOfUserPermission ) -// CheckRead returns true if user owns read permission. -func (up *UserPermission) CheckRead() bool { - return *up >= Admin && *up < NumberOfUserPermission +// UserPermissionFromRole construct a new user permission instance from primitive user permission role enum. +func UserPermissionFromRole(role UserPermissionRole) *UserPermission { + return &UserPermission{ + Role: role, + } +} + +// HasReadPermission returns true if user owns read permission. +func (up *UserPermission) HasReadPermission() bool { + if up == nil { + return false + } + return up.Role >= Admin && up.Role < NumberOfUserPermission +} + +// HasWritePermission returns true if user owns write permission. +func (up *UserPermission) HasWritePermission() bool { + if up == nil { + return false + } + return up.Role >= Admin && up.Role <= Write } -// CheckWrite returns true if user owns write permission. -func (up *UserPermission) CheckWrite() bool { - return *up >= Admin && *up <= Write +// HasAdminPermission returns true if user owns admin permission. +func (up *UserPermission) HasAdminPermission() bool { + if up == nil { + return false + } + return up.Role == Admin } -// CheckAdmin returns true if user owns admin permission. -func (up *UserPermission) CheckAdmin() bool { - return *up == Admin +// IsValid returns whether the permission object is valid or not. +func (up *UserPermission) IsValid() bool { + return up != nil && up.Role < NumberOfUserPermission && up.Role >= Admin } -// Valid returns true if the value is a meaning permission value. -func (up *UserPermission) Valid() bool { - return *up >= Admin && *up < NumberOfUserPermission +// HasDisallowedQueryPatterns returns whether the queries are permitted. +func (up *UserPermission) HasDisallowedQueryPatterns(queries []Query) (query string, status bool) { + if up == nil { + status = true + return + } + if len(up.Patterns) == 0 { + status = false + return + } + + // more queries than patterns + queryMap := make(map[string]bool, len(up.Patterns)) + for _, p := range up.Patterns { + queryMap[p] = true + } + for _, q := range queries { + if !queryMap[q.Pattern] { + // not permitted + query = q.Pattern + status = true + break + } + } + + return } // FromString converts string to UserPermission. func (up *UserPermission) FromString(perm string) { switch perm { case "Admin": - *up = Admin + up.Role = Admin case "Write": - *up = Write + up.Role = Write case "Read": - *up = Read + up.Role = Read case "Void": - *up = Void + up.Role = Void default: - *up = NumberOfUserPermission + up.Role = NumberOfUserPermission } } @@ -113,14 +166,14 @@ func (s *Status) EnableQuery() bool { // PermStat defines the permissions status structure. type PermStat struct { - Permission UserPermission + Permission *UserPermission Status Status } // SQLChainUser defines a SQLChain user. type SQLChainUser struct { Address proto.AccountAddress - Permission UserPermission + Permission *UserPermission AdvancePayment uint64 Arrears uint64 Deposit uint64 diff --git a/types/account_gen.go b/types/account_gen.go index 1e7a3acbd..320d3ebd0 100644 --- a/types/account_gen.go +++ b/types/account_gen.go @@ -247,14 +247,33 @@ func (z *SQLChainUser) MarshalHash() (o []byte, err error) { o = hsp.AppendUint64(o, z.AdvancePayment) o = hsp.AppendUint64(o, z.Arrears) o = hsp.AppendUint64(o, z.Deposit) - o = hsp.AppendInt32(o, int32(z.Permission)) + if z.Permission == nil { + o = hsp.AppendNil(o) + } else { + // map header, size 2 + o = append(o, 0x82) + o = hsp.AppendInt32(o, int32(z.Permission.Role)) + o = hsp.AppendArrayHeader(o, uint32(len(z.Permission.Patterns))) + for za0001 := range z.Permission.Patterns { + o = hsp.AppendString(o, z.Permission.Patterns[za0001]) + } + } o = hsp.AppendInt32(o, int32(z.Status)) return } // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *SQLChainUser) Msgsize() (s int) { - s = 1 + 8 + z.Address.Msgsize() + 15 + hsp.Uint64Size + 8 + hsp.Uint64Size + 8 + hsp.Uint64Size + 11 + hsp.Int32Size + 7 + hsp.Int32Size + s = 1 + 8 + z.Address.Msgsize() + 15 + hsp.Uint64Size + 8 + hsp.Uint64Size + 8 + hsp.Uint64Size + 11 + if z.Permission == nil { + s += hsp.NilSize + } else { + s += 1 + 5 + hsp.Int32Size + 9 + hsp.ArrayHeaderSize + for za0001 := range z.Permission.Patterns { + s += hsp.StringPrefixSize + len(z.Permission.Patterns[za0001]) + } + } + s += 7 + hsp.Int32Size return } @@ -294,7 +313,31 @@ func (z *UserArrears) Msgsize() (s int) { } // MarshalHash marshals for hash -func (z UserPermission) MarshalHash() (o []byte, err error) { +func (z *UserPermission) MarshalHash() (o []byte, err error) { + var b []byte + o = hsp.Require(b, z.Msgsize()) + // map header, size 2 + o = append(o, 0x82) + o = hsp.AppendArrayHeader(o, uint32(len(z.Patterns))) + for za0001 := range z.Patterns { + o = hsp.AppendString(o, z.Patterns[za0001]) + } + o = hsp.AppendInt32(o, int32(z.Role)) + return +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *UserPermission) Msgsize() (s int) { + s = 1 + 9 + hsp.ArrayHeaderSize + for za0001 := range z.Patterns { + s += hsp.StringPrefixSize + len(z.Patterns[za0001]) + } + s += 5 + hsp.Int32Size + return +} + +// MarshalHash marshals for hash +func (z UserPermissionRole) MarshalHash() (o []byte, err error) { var b []byte o = hsp.Require(b, z.Msgsize()) o = hsp.AppendInt32(o, int32(z)) @@ -302,7 +345,7 @@ func (z UserPermission) MarshalHash() (o []byte, err error) { } // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z UserPermission) Msgsize() (s int) { +func (z UserPermissionRole) Msgsize() (s int) { s = hsp.Int32Size return } diff --git a/types/account_gen_test.go b/types/account_gen_test.go index 30e9ad803..388a19ddb 100644 --- a/types/account_gen_test.go +++ b/types/account_gen_test.go @@ -230,3 +230,40 @@ func BenchmarkAppendMsgUserArrears(b *testing.B) { bts, _ = v.MarshalHash() } } + +func TestMarshalHashUserPermission(t *testing.T) { + v := UserPermission{} + binary.Read(rand.Reader, binary.BigEndian, &v) + bts1, err := v.MarshalHash() + if err != nil { + t.Fatal(err) + } + bts2, err := v.MarshalHash() + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(bts1, bts2) { + t.Fatal("hash not stable") + } +} + +func BenchmarkMarshalHashUserPermission(b *testing.B) { + v := UserPermission{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalHash() + } +} + +func BenchmarkAppendMsgUserPermission(b *testing.B) { + v := UserPermission{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalHash() + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalHash() + } +} diff --git a/types/updatepermission.go b/types/updatepermission.go index 1b7ed46a6..729829c3d 100644 --- a/types/updatepermission.go +++ b/types/updatepermission.go @@ -30,7 +30,7 @@ import ( type UpdatePermissionHeader struct { TargetSQLChain proto.AccountAddress TargetUser proto.AccountAddress - Permission UserPermission + Permission *UserPermission Nonce interfaces.AccountNonce } diff --git a/types/updatepermission_gen.go b/types/updatepermission_gen.go index 9c54d6e2f..11ba931d5 100644 --- a/types/updatepermission_gen.go +++ b/types/updatepermission_gen.go @@ -47,10 +47,14 @@ func (z *UpdatePermissionHeader) MarshalHash() (o []byte, err error) { } else { o = hsp.AppendBytes(o, oTemp) } - if oTemp, err := z.Permission.MarshalHash(); err != nil { - return nil, err + if z.Permission == nil { + o = hsp.AppendNil(o) } else { - o = hsp.AppendBytes(o, oTemp) + if oTemp, err := z.Permission.MarshalHash(); err != nil { + return nil, err + } else { + o = hsp.AppendBytes(o, oTemp) + } } if oTemp, err := z.TargetSQLChain.MarshalHash(); err != nil { return nil, err @@ -67,6 +71,12 @@ func (z *UpdatePermissionHeader) MarshalHash() (o []byte, err error) { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *UpdatePermissionHeader) Msgsize() (s int) { - s = 1 + 6 + z.Nonce.Msgsize() + 11 + z.Permission.Msgsize() + 15 + z.TargetSQLChain.Msgsize() + 11 + z.TargetUser.Msgsize() + s = 1 + 6 + z.Nonce.Msgsize() + 11 + if z.Permission == nil { + s += hsp.NilSize + } else { + s += z.Permission.Msgsize() + } + s += 15 + z.TargetSQLChain.Msgsize() + 11 + z.TargetUser.Msgsize() return } diff --git a/types/xxx_test.go b/types/xxx_test.go index ab9d8f77f..cf2454ab6 100644 --- a/types/xxx_test.go +++ b/types/xxx_test.go @@ -68,7 +68,6 @@ func generateRandomBlock(parent hash.Hash, isGenesis bool) (b *BPBlock, err erro if err != nil { return - } h := hash.Hash{} @@ -96,8 +95,8 @@ func generateRandomBlock(parent hash.Hash, isGenesis bool) (b *BPBlock, err erro } err = b.PackAndSignBlock(priv) - return + return } func generateRandomBillingRequestHeader() *BillingRequestHeader { @@ -109,7 +108,6 @@ func generateRandomBillingRequestHeader() *BillingRequestHeader { HighHeight: rand.Int31(), GasAmounts: generateRandomGasAmount(peerNum), } - } func generateRandomBillingRequest() (req *BillingRequest, err error) { @@ -119,7 +117,6 @@ func generateRandomBillingRequest() (req *BillingRequest, err error) { } if _, err = req.PackRequestHeader(); err != nil { return nil, err - } for i := 0; i < peerNum; i++ { @@ -128,36 +125,29 @@ func generateRandomBillingRequest() (req *BillingRequest, err error) { if priv, _, err = asymmetric.GenSecp256k1KeyPair(); err != nil { return - } if _, _, err = req.SignRequestHeader(priv, false); err != nil { return - } - } return - } func generateRandomBillingHeader() (tc *BillingHeader, err error) { var req *BillingRequest if req, err = generateRandomBillingRequest(); err != nil { return - } var priv *asymmetric.PrivateKey if priv, _, err = asymmetric.GenSecp256k1KeyPair(); err != nil { return - } if _, _, err = req.SignRequestHeader(priv, false); err != nil { return - } receivers := make([]*proto.AccountAddress, peerNum) @@ -169,33 +159,27 @@ func generateRandomBillingHeader() (tc *BillingHeader, err error) { receivers[i] = &accountAddress fees[i] = rand.Uint64() rewards[i] = rand.Uint64() - } producer := proto.AccountAddress(generateRandomHash()) tc = NewBillingHeader(pi.AccountNonce(rand.Uint32()), req, producer, receivers, fees, rewards) return tc, nil - } func generateRandomBilling() (*Billing, error) { header, err := generateRandomBillingHeader() if err != nil { return nil, err - } priv, _, err := asymmetric.GenSecp256k1KeyPair() if err != nil { return nil, err - } txBilling := NewBilling(header) if err := txBilling.Sign(priv); err != nil { return nil, err - } return txBilling, nil - } func generateRandomGasAmount(n int) []*proto.AddrAndGas { @@ -207,11 +191,9 @@ func generateRandomGasAmount(n int) []*proto.AddrAndGas { RawNodeID: proto.RawNodeID{Hash: generateRandomHash()}, GasAmount: rand.Uint64(), } - } return gasAmount - } func randBytes(n int) (b []byte) { diff --git a/worker/chainbusservice_test.go b/worker/chainbusservice_test.go index 2429061e6..8fd721e2d 100644 --- a/worker/chainbusservice_test.go +++ b/worker/chainbusservice_test.go @@ -94,7 +94,7 @@ func TestNewBusService(t *testing.T) { permStat, ok := bs.RequestPermStat(profile.ID, testAddr) So(ok, ShouldBeTrue) So(permStat.Status, ShouldEqual, profile.Users[0].Status) - So(permStat.Permission, ShouldEqual, profile.Users[0].Permission) + So(permStat.Permission, ShouldResemble, profile.Users[0].Permission) permStat, ok = bs.RequestPermStat(profile.ID, testNotExistAddr) } p, ok := bs.RequestSQLProfile(testNotExistID) @@ -116,7 +116,7 @@ func TestNewBusService(t *testing.T) { permStat, ok := bs.RequestPermStat(profile.ID, testAddr) So(ok, ShouldBeTrue) So(permStat.Status, ShouldEqual, profile.Users[0].Status) - So(permStat.Permission, ShouldEqual, profile.Users[0].Permission) + So(permStat.Permission, ShouldResemble, profile.Users[0].Permission) permStat, ok = bs.RequestPermStat(profile.ID, testNotExistAddr) } p, ok := bs.RequestSQLProfile(testNotExistID) diff --git a/worker/dbms.go b/worker/dbms.go index df094218c..028025b84 100644 --- a/worker/dbms.go +++ b/worker/dbms.go @@ -445,7 +445,7 @@ func (dbms *DBMS) Query(req *types.Request) (res *types.Response, err error) { if err != nil { return } - err = dbms.checkPermission(addr, req.Header.DatabaseID, req.Header.QueryType) + err = dbms.checkPermission(addr, req.Header.DatabaseID, req.Header.QueryType, req.Payload.Queries) if err != nil { return } @@ -509,32 +509,59 @@ func (dbms *DBMS) removeMeta(dbID proto.DatabaseID) (err error) { } func (dbms *DBMS) checkPermission(addr proto.AccountAddress, - dbID proto.DatabaseID, queryType types.QueryType) (err error) { + dbID proto.DatabaseID, queryType types.QueryType, queries []types.Query) (err error) { log.Debugf("in checkPermission, database id: %s, user addr: %s", dbID, addr.String()) - if permStat, ok := dbms.busService.RequestPermStat(dbID, addr); ok { - if !permStat.Status.EnableQuery() { - err = errors.Wrapf(ErrPermissionDeny, "cannot query, status: %d", permStat.Status) + var ( + permStat *types.PermStat + ok bool + ) + + // get database perm stat + permStat, ok = dbms.busService.RequestPermStat(dbID, addr) + + // perm stat not exists + if !ok { + err = errors.Wrap(ErrPermissionDeny, "database not exists") + return + } + + // check if query is enabled + if !permStat.Status.EnableQuery() { + err = errors.Wrapf(ErrPermissionDeny, "cannot query, status: %d", permStat.Status) + return + } + + // check query type permission + switch queryType { + case types.ReadQuery: + if !permStat.Permission.HasReadPermission() { + err = errors.Wrapf(ErrPermissionDeny, "cannot read, permission: %d", permStat.Permission) return } - if queryType == types.ReadQuery { - if !permStat.Permission.CheckRead() { - err = errors.Wrapf(ErrPermissionDeny, "cannot read, permission: %d", permStat.Permission) - return - } - } else if queryType == types.WriteQuery { - if !permStat.Permission.CheckWrite() { - err = errors.Wrapf(ErrPermissionDeny, "cannot write, permission: %d", permStat.Permission) - return - } - } else { - err = errors.Wrapf(ErrInvalidPermission, - "invalid permission, permission: %d", permStat.Permission) + case types.WriteQuery: + if !permStat.Permission.HasWritePermission() { + err = errors.Wrapf(ErrPermissionDeny, "cannot write, permission: %d", permStat.Permission) return - } - } else { - err = errors.Wrap(ErrPermissionDeny, "database not exists") + default: + err = errors.Wrapf(ErrInvalidPermission, + "invalid permission, permission: %d", permStat.Permission) + return + } + + // check for query pattern + var ( + disallowedQuery string + hasDisallowedQuery bool + ) + + if disallowedQuery, hasDisallowedQuery = permStat.Permission.HasDisallowedQueryPatterns(queries); hasDisallowedQuery { + err = errors.Wrapf(ErrPermissionDeny, "disallowed query %s", disallowedQuery) + log.WithError(err).WithFields(log.Fields{ + "permission": permStat.Permission, + "query": disallowedQuery, + }).Debug("can not query") return } @@ -548,7 +575,7 @@ func (dbms *DBMS) addTxSubscription(dbID proto.DatabaseID, nodeID proto.NodeID, log.WithFields(log.Fields{ "databaseID": dbID, "nodeID": nodeID, - }).WithError(err).Warning("get pubkey failed in addTxSubscription") + }).WithError(err).Warning("get public key failed in addTxSubscription") return } addr, err := crypto.PubKeyHash(pubkey) @@ -567,7 +594,7 @@ func (dbms *DBMS) addTxSubscription(dbID proto.DatabaseID, nodeID proto.NodeID, "startHeight": startHeight, }).Debugf("addTxSubscription") - err = dbms.checkPermission(addr, dbID, types.ReadQuery) + err = dbms.checkPermission(addr, dbID, types.ReadQuery, nil) if err != nil { log.WithFields(log.Fields{"databaseID": dbID, "addr": addr}).WithError(err).Warning("permission deny") return diff --git a/worker/dbms_test.go b/worker/dbms_test.go index 4895b024f..db8d80cbe 100644 --- a/worker/dbms_test.go +++ b/worker/dbms_test.go @@ -134,11 +134,12 @@ func TestDBMS(t *testing.T) { // grant write and read permission err = dbms.UpdatePermission(dbAddr.DatabaseID(), userAddr, - &types.PermStat{Permission: types.Write, Status: types.Normal}) + &types.PermStat{Permission: types.UserPermissionFromRole(types.Write), Status: types.Normal}) So(err, ShouldBeNil) userState, ok := dbms.busService.RequestPermStat(dbAddr.DatabaseID(), userAddr) So(ok, ShouldBeTrue) - So(userState.Permission, ShouldEqual, types.Write) + So(userState.Permission, ShouldNotBeNil) + So(userState.Permission.Role, ShouldEqual, types.Write) So(userState.Status, ShouldEqual, types.Normal) Convey("success write and read", func() { @@ -193,10 +194,11 @@ func TestDBMS(t *testing.T) { // revoke write permission err = dbms.UpdatePermission(dbAddr.DatabaseID(), userAddr, - &types.PermStat{Permission: types.Read, Status: types.Normal}) + &types.PermStat{Permission: types.UserPermissionFromRole(types.Read), Status: types.Normal}) userState, ok := dbms.busService.RequestPermStat(dbAddr.DatabaseID(), userAddr) So(ok, ShouldBeTrue) - So(userState.Permission, ShouldEqual, types.Read) + So(userState.Permission, ShouldNotBeNil) + So(userState.Permission.Role, ShouldEqual, types.Read) So(userState.Status, ShouldEqual, types.Normal) Convey("success reading and fail to write", func() { @@ -229,10 +231,12 @@ func TestDBMS(t *testing.T) { // grant invalid permission err = dbms.UpdatePermission(dbAddr.DatabaseID(), userAddr, - &types.PermStat{Permission: types.Void, Status: types.Normal}) + &types.PermStat{Permission: types.UserPermissionFromRole(types.Void), Status: types.Normal}) + So(err, ShouldBeNil) userState, ok = dbms.busService.RequestPermStat(dbAddr.DatabaseID(), userAddr) So(ok, ShouldBeTrue) - So(userState.Permission, ShouldEqual, types.Void) + So(userState.Permission, ShouldNotBeNil) + So(userState.Permission.Role, ShouldEqual, types.Void) So(userState.Status, ShouldEqual, types.Normal) Convey("invalid permission query should fail", func() { @@ -264,10 +268,12 @@ func TestDBMS(t *testing.T) { // grant admin permission but in arrears err = dbms.UpdatePermission(dbAddr.DatabaseID(), userAddr, - &types.PermStat{Permission: types.Admin, Status: types.Arrears}) + &types.PermStat{Permission: types.UserPermissionFromRole(types.Admin), Status: types.Arrears}) + So(err, ShouldBeNil) userState, ok = dbms.busService.RequestPermStat(dbAddr.DatabaseID(), userAddr) So(ok, ShouldBeTrue) - So(userState.Permission, ShouldEqual, types.Admin) + So(userState.Permission, ShouldNotBeNil) + So(userState.Permission.Role, ShouldEqual, types.Admin) So(userState.Status, ShouldEqual, types.Arrears) Convey("arrears query should fail", func() { @@ -296,10 +302,12 @@ func TestDBMS(t *testing.T) { // switch user to normal err = dbms.UpdatePermission(dbAddr.DatabaseID(), userAddr, - &types.PermStat{Permission: types.Admin, Status: types.Normal}) + &types.PermStat{Permission: types.UserPermissionFromRole(types.Admin), Status: types.Normal}) + So(err, ShouldBeNil) userState, ok = dbms.busService.RequestPermStat(dbAddr.DatabaseID(), userAddr) So(ok, ShouldBeTrue) - So(userState.Permission, ShouldEqual, types.Admin) + So(userState.Permission, ShouldNotBeNil) + So(userState.Permission.Role, ShouldEqual, types.Admin) So(userState.Status, ShouldEqual, types.Normal) Convey("can send read and write queries", func() { @@ -346,6 +354,101 @@ func TestDBMS(t *testing.T) { So(err, ShouldBeNil) }) + // enforce query pattern regulations + err = dbms.UpdatePermission(dbAddr.DatabaseID(), userAddr, + &types.PermStat{Permission: &types.UserPermission{ + Role: types.Admin, + Patterns: []string{ + "create table test (test int)", + "SELECT 1", + "INSERT INTO TEST VALUES(1)", + }, + }, Status: types.Normal}) + So(err, ShouldBeNil) + userState, ok = dbms.busService.RequestPermStat(dbAddr.DatabaseID(), userAddr) + So(ok, ShouldBeTrue) + So(userState.Permission, ShouldNotBeNil) + So(userState.Permission.Role, ShouldEqual, types.Admin) + So(userState.Permission.Patterns, ShouldHaveLength, 3) + + Convey("query patterns restrictions", func() { + var writeQuery *types.Request + var queryRes *types.Response + + // sending allowed write query + writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, 1, 11, dbID, []string{ + "create table test (test int)", + "INSERT INTO TEST VALUES(1)", + }) + So(err, ShouldBeNil) + + err = testRequest(route.DBSQuery, writeQuery, &queryRes) + So(err, ShouldBeNil) + err = queryRes.Verify() + So(err, ShouldBeNil) + So(queryRes.Header.RowCount, ShouldEqual, 0) + + // sending allowed read query + var readQuery *types.Request + readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, 1, 12, dbID, []string{ + "SELECT 1", + }) + So(err, ShouldBeNil) + + err = testRequest(route.DBSQuery, readQuery, &queryRes) + So(err, ShouldBeNil) + err = queryRes.Verify() + So(err, ShouldBeNil) + So(queryRes.Header.RowCount, ShouldEqual, uint64(1)) + So(queryRes.Payload.Rows, ShouldHaveLength, 1) + So(queryRes.Payload.Rows[0].Values, ShouldHaveLength, 1) + So(queryRes.Payload.Rows[0].Values[0], ShouldEqual, 1) + + // sending disallowed write query + writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, 1, 13, dbID, []string{ + "insert into test values(1)", + }) + So(err, ShouldBeNil) + err = testRequest(route.DBSQuery, writeQuery, &queryRes) + So(err, ShouldNotBeNil) + + // sending disallowed write query mixed with valid write query + writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, 1, 14, dbID, []string{ + "INSERT INTO TEST VALUES(1)", + "insert into test values(1)", + }) + So(err, ShouldBeNil) + err = testRequest(route.DBSQuery, writeQuery, &queryRes) + So(err, ShouldNotBeNil) + + // sending disallowed read query + readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, 1, 15, dbID, []string{ + "select * from test", + }) + So(err, ShouldBeNil) + err = testRequest(route.DBSQuery, readQuery, &queryRes) + So(err, ShouldNotBeNil) + + // sending disallowed read query + readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, 1, 16, dbID, []string{ + "SELECT 1", + "select * from test", + }) + So(err, ShouldBeNil) + err = testRequest(route.DBSQuery, readQuery, &queryRes) + So(err, ShouldNotBeNil) + }) + + // set back permission object + err = dbms.UpdatePermission(dbAddr.DatabaseID(), userAddr, + &types.PermStat{Permission: types.UserPermissionFromRole(types.Admin), Status: types.Normal}) + So(err, ShouldBeNil) + userState, ok = dbms.busService.RequestPermStat(dbAddr.DatabaseID(), userAddr) + So(ok, ShouldBeTrue) + So(userState.Permission, ShouldNotBeNil) + So(userState.Permission.Role, ShouldEqual, types.Admin) + So(userState.Status, ShouldEqual, types.Normal) + Convey("query non-existent database", func() { // sending write query var writeQuery *types.Request diff --git a/worker/helper_test.go b/worker/helper_test.go index e7b48b14f..d561f2ed6 100644 --- a/worker/helper_test.go +++ b/worker/helper_test.go @@ -100,22 +100,22 @@ var ( testNotExistAddr = proto.AccountAddress(hash.THashH([]byte{'a', 'a'})) testUser1 = &types.SQLChainUser{ Address: testAddr, - Permission: types.Write, + Permission: types.UserPermissionFromRole(types.Write), Status: types.Normal, } testUser2 = &types.SQLChainUser{ Address: testAddr, - Permission: types.Read, + Permission: types.UserPermissionFromRole(types.Read), Status: types.Arrears, } testUser3 = &types.SQLChainUser{ Address: testAddr, - Permission: types.Write, + Permission: types.UserPermissionFromRole(types.Write), Status: types.Reminder, } testUser4 = &types.SQLChainUser{ Address: testAddr, - Permission: types.Read, + Permission: types.UserPermissionFromRole(types.Read), Status: types.Arbitration, } ) From 816decd4640d36d2c226310a22dccb16b186adc9 Mon Sep 17 00:00:00 2001 From: Qi Xiao Date: Thu, 27 Dec 2018 18:56:03 +0800 Subject: [PATCH 02/10] Use cache for query pattern permission matching --- types/account.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/types/account.go b/types/account.go index 299041ba5..429eed134 100644 --- a/types/account.go +++ b/types/account.go @@ -17,6 +17,8 @@ package types import ( + "sync" + pi "github.com/CovenantSQL/CovenantSQL/blockproducer/interfaces" "github.com/CovenantSQL/CovenantSQL/proto" ) @@ -46,6 +48,10 @@ type UserPermission struct { // SQL pattern regulations for user queries // only a fully matched (case-sensitive) sql query is permitted to execute. Patterns []string + + // patterns map cache for matching + cachedPatternMapOnce sync.Once + cachedPatternMap map[string]bool } const ( @@ -108,13 +114,15 @@ func (up *UserPermission) HasDisallowedQueryPatterns(queries []Query) (query str return } - // more queries than patterns - queryMap := make(map[string]bool, len(up.Patterns)) - for _, p := range up.Patterns { - queryMap[p] = true - } + up.cachedPatternMapOnce.Do(func() { + up.cachedPatternMap = make(map[string]bool, len(up.Patterns)) + for _, p := range up.Patterns { + up.cachedPatternMap[p] = true + } + }) + for _, q := range queries { - if !queryMap[q.Pattern] { + if !up.cachedPatternMap[q.Pattern] { // not permitted query = q.Pattern status = true From b68c1ecda54379ee5b1258805ac9587eb675e2eb Mon Sep 17 00:00:00 2001 From: Qi Xiao Date: Mon, 7 Jan 2019 23:12:16 +0800 Subject: [PATCH 03/10] Make cql updatePermission feature compatible with sql pattern config --- cmd/cql/main.go | 20 +++++---- types/account.go | 68 +++++++++++++++++++++++-------- types/account_test.go | 95 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 25 deletions(-) create mode 100644 types/account_test.go diff --git a/cmd/cql/main.go b/cmd/cql/main.go index 547b2ba58..ea78c1e54 100644 --- a/cmd/cql/main.go +++ b/cmd/cql/main.go @@ -80,7 +80,7 @@ var ( type userPermission struct { TargetChain proto.AccountAddress `json:"chain"` TargetUser proto.AccountAddress `json:"user"` - Perm string `json:"perm"` + Perm json.RawMessage `json:"perm"` } type tranToken struct { @@ -371,15 +371,21 @@ func main() { } var p types.UserPermission - p.FromString(perm.Perm) - if p.Role > types.NumberOfUserPermission { - log.WithError(err).Errorf("update permission failed: invalid permission description") - os.Exit(-1) - return + + if err := json.Unmarshal(perm.Perm, &p); err != nil { + // try again using role string representation + if err := json.Unmarshal(perm.Perm, &p.Role); err != nil { + log.WithError(err).Errorf("update permission failed: invalid permission description") + os.Exit(-1) + return + } else if !p.IsValid() { + log.Errorf("update permission failed: invalid permission description") + os.Exit(-1) + return + } } txHash, err := client.UpdatePermission(perm.TargetUser, perm.TargetChain, &p) - if err != nil { log.WithError(err).Error("update permission failed") os.Exit(-1) diff --git a/types/account.go b/types/account.go index 429eed134..34549ff06 100644 --- a/types/account.go +++ b/types/account.go @@ -17,6 +17,7 @@ package types import ( + "encoding/json" "sync" pi "github.com/CovenantSQL/CovenantSQL/blockproducer/interfaces" @@ -44,10 +45,10 @@ type UserPermissionRole int32 // UserPermission defines permissions of a SQLChain user. type UserPermission struct { // User role to access database. - Role UserPermissionRole + Role UserPermissionRole `json:"role"` // SQL pattern regulations for user queries // only a fully matched (case-sensitive) sql query is permitted to execute. - Patterns []string + Patterns []string `json:"patterns"` // patterns map cache for matching cachedPatternMapOnce sync.Once @@ -67,6 +68,53 @@ const ( NumberOfUserPermission ) +// UnmarshalJSON implements the json.Unmarshler interface. +func (r *UserPermissionRole) UnmarshalJSON(data []byte) (err error) { + var s string + if err = json.Unmarshal(data, &s); err != nil { + return + } + r.FromString(s) + return +} + +// MarshalJSON implements the json.Marshaler interface. +func (r UserPermissionRole) MarshalJSON() ([]byte, error) { + return json.Marshal(r.String()) +} + +// String implements the fmt.Stringer interface. +func (r UserPermissionRole) String() string { + switch r { + case Admin: + return "Admin" + case Write: + return "Write" + case Read: + return "Read" + case Void: + return "Void" + default: + return "Unknown" + } +} + +// FromString converts string to UserPermissionRole. +func (r *UserPermissionRole) FromString(perm string) { + switch perm { + case "Admin": + *r = Admin + case "Write": + *r = Write + case "Read": + *r = Read + case "Void": + *r = Void + default: + *r = NumberOfUserPermission + } +} + // UserPermissionFromRole construct a new user permission instance from primitive user permission role enum. func UserPermissionFromRole(role UserPermissionRole) *UserPermission { return &UserPermission{ @@ -133,22 +181,6 @@ func (up *UserPermission) HasDisallowedQueryPatterns(queries []Query) (query str return } -// FromString converts string to UserPermission. -func (up *UserPermission) FromString(perm string) { - switch perm { - case "Admin": - up.Role = Admin - case "Write": - up.Role = Write - case "Read": - up.Role = Read - case "Void": - up.Role = Void - default: - up.Role = NumberOfUserPermission - } -} - // Status defines status of a SQLChain user/miner. type Status int32 diff --git a/types/account_test.go b/types/account_test.go new file mode 100644 index 000000000..7a7eb665e --- /dev/null +++ b/types/account_test.go @@ -0,0 +1,95 @@ +/* + * Copyright 2019 The CovenantSQL Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package types + +import ( + "encoding/json" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestUserPermissionFromRole(t *testing.T) { + Convey("test marshal/unmarshal json", t, func() { + jsonBytes, err := json.Marshal(Read) + So(err, ShouldBeNil) + So(jsonBytes, ShouldResemble, []byte(`"Read"`)) + var r UserPermissionRole + So(r, ShouldEqual, Void) + err = json.Unmarshal([]byte(`"Write"`), &r) + So(err, ShouldBeNil) + So(r, ShouldEqual, Write) + }) + Convey("test string/from string", t, func() { + var r UserPermissionRole + So(r, ShouldEqual, Void) + r.FromString(Read.String()) + So(r, ShouldEqual, Read) + }) +} + +func TestUserPermission(t *testing.T) { + Convey("nil protect", t, func() { + p := (*UserPermission)(nil) + So(p.HasReadPermission(), ShouldBeFalse) + So(p.HasWritePermission(), ShouldBeFalse) + So(p.HasAdminPermission(), ShouldBeFalse) + So(p.IsValid(), ShouldBeFalse) + _, state := p.HasDisallowedQueryPatterns([]Query{}) + So(state, ShouldBeTrue) + }) + Convey("has read permission", t, func() { + So(UserPermissionFromRole(Void).HasReadPermission(), ShouldBeFalse) + So(UserPermissionFromRole(Read).HasReadPermission(), ShouldBeTrue) + So(UserPermissionFromRole(Write).HasReadPermission(), ShouldBeTrue) + So(UserPermissionFromRole(Admin).HasReadPermission(), ShouldBeTrue) + So(UserPermissionFromRole(NumberOfUserPermission).HasReadPermission(), ShouldBeFalse) + }) + Convey("has write permission", t, func() { + So(UserPermissionFromRole(Void).HasWritePermission(), ShouldBeFalse) + So(UserPermissionFromRole(Read).HasWritePermission(), ShouldBeFalse) + So(UserPermissionFromRole(Write).HasWritePermission(), ShouldBeTrue) + So(UserPermissionFromRole(Admin).HasWritePermission(), ShouldBeTrue) + So(UserPermissionFromRole(NumberOfUserPermission).HasWritePermission(), ShouldBeFalse) + }) + Convey("has admin permission", t, func() { + So(UserPermissionFromRole(Void).HasAdminPermission(), ShouldBeFalse) + So(UserPermissionFromRole(Read).HasAdminPermission(), ShouldBeFalse) + So(UserPermissionFromRole(Write).HasAdminPermission(), ShouldBeFalse) + So(UserPermissionFromRole(Admin).HasAdminPermission(), ShouldBeTrue) + So(UserPermissionFromRole(NumberOfUserPermission).HasAdminPermission(), ShouldBeFalse) + }) + Convey("is valid", t, func() { + So(UserPermissionFromRole(Void).IsValid(), ShouldBeFalse) + So(UserPermissionFromRole(Read).IsValid(), ShouldBeTrue) + So(UserPermissionFromRole(Write).IsValid(), ShouldBeTrue) + So(UserPermissionFromRole(Admin).IsValid(), ShouldBeTrue) + So(UserPermissionFromRole(NumberOfUserPermission).IsValid(), ShouldBeFalse) + }) + Convey("query patterns", t, func() { + // empty patterns limitation + _, state := UserPermissionFromRole(Read).HasDisallowedQueryPatterns([]Query{ + { + Pattern: "select 1", + }, + { + Pattern: "insert into test values(1)", + }, + }) + So(state, ShouldBeFalse) + }) +} From f3d2259b1218c7f5ddc94a6822c40f25a60d2bdb Mon Sep 17 00:00:00 2001 From: Qi Xiao Date: Mon, 7 Jan 2019 23:38:39 +0800 Subject: [PATCH 04/10] Move updatePermission json tag declaration into cql command package --- cmd/cql/main.go | 31 +++++++++++++++++++++++-------- types/account.go | 4 ++-- types/account_gen.go | 32 ++++++++++++++++++++++++++++++++ types/account_gen_test.go | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 10 deletions(-) diff --git a/cmd/cql/main.go b/cmd/cql/main.go index ea78c1e54..825e9f669 100644 --- a/cmd/cql/main.go +++ b/cmd/cql/main.go @@ -83,6 +83,14 @@ type userPermission struct { Perm json.RawMessage `json:"perm"` } +type userPermPayload struct { + // User role to access database. + Role types.UserPermissionRole `json:"role"` + // SQL pattern regulations for user queries + // only a fully matched (case-sensitive) sql query is permitted to execute. + Patterns []string `json:"patterns"` +} + type tranToken struct { TargetUser proto.AccountAddress `json:"addr"` Amount string `json:"amount"` @@ -370,22 +378,29 @@ func main() { return } - var p types.UserPermission + var permPayload userPermPayload - if err := json.Unmarshal(perm.Perm, &p); err != nil { + if err := json.Unmarshal(perm.Perm, &permPayload); err != nil { // try again using role string representation - if err := json.Unmarshal(perm.Perm, &p.Role); err != nil { + if err := json.Unmarshal(perm.Perm, &permPayload.Role); err != nil { log.WithError(err).Errorf("update permission failed: invalid permission description") os.Exit(-1) return - } else if !p.IsValid() { - log.Errorf("update permission failed: invalid permission description") - os.Exit(-1) - return } } - txHash, err := client.UpdatePermission(perm.TargetUser, perm.TargetChain, &p) + p := &types.UserPermission{ + Role: permPayload.Role, + Patterns: permPayload.Patterns, + } + + if !p.IsValid() { + log.Errorf("update permission failed: invalid permission description") + os.Exit(-1) + return + } + + txHash, err := client.UpdatePermission(perm.TargetUser, perm.TargetChain, p) if err != nil { log.WithError(err).Error("update permission failed") os.Exit(-1) diff --git a/types/account.go b/types/account.go index 34549ff06..dce6b1449 100644 --- a/types/account.go +++ b/types/account.go @@ -45,10 +45,10 @@ type UserPermissionRole int32 // UserPermission defines permissions of a SQLChain user. type UserPermission struct { // User role to access database. - Role UserPermissionRole `json:"role"` + Role UserPermissionRole // SQL pattern regulations for user queries // only a fully matched (case-sensitive) sql query is permitted to execute. - Patterns []string `json:"patterns"` + Patterns []string // patterns map cache for matching cachedPatternMapOnce sync.Once diff --git a/types/account_gen.go b/types/account_gen.go index 320d3ebd0..2807344b4 100644 --- a/types/account_gen.go +++ b/types/account_gen.go @@ -89,6 +89,38 @@ func (z *MinerInfo) Msgsize() (s int) { return } +// MarshalHash marshals for hash +func (z *PermStat) MarshalHash() (o []byte, err error) { + var b []byte + o = hsp.Require(b, z.Msgsize()) + // map header, size 2 + o = append(o, 0x82, 0x82) + if z.Permission == nil { + o = hsp.AppendNil(o) + } else { + if oTemp, err := z.Permission.MarshalHash(); err != nil { + return nil, err + } else { + o = hsp.AppendBytes(o, oTemp) + } + } + o = append(o, 0x82) + o = hsp.AppendInt32(o, int32(z.Status)) + return +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *PermStat) Msgsize() (s int) { + s = 1 + 11 + if z.Permission == nil { + s += hsp.NilSize + } else { + s += z.Permission.Msgsize() + } + s += 7 + hsp.Int32Size + return +} + // MarshalHash marshals for hash func (z *ProviderProfile) MarshalHash() (o []byte, err error) { var b []byte diff --git a/types/account_gen_test.go b/types/account_gen_test.go index 388a19ddb..9b6a8a5d3 100644 --- a/types/account_gen_test.go +++ b/types/account_gen_test.go @@ -83,6 +83,43 @@ func BenchmarkAppendMsgMinerInfo(b *testing.B) { } } +func TestMarshalHashPermStat(t *testing.T) { + v := PermStat{} + binary.Read(rand.Reader, binary.BigEndian, &v) + bts1, err := v.MarshalHash() + if err != nil { + t.Fatal(err) + } + bts2, err := v.MarshalHash() + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(bts1, bts2) { + t.Fatal("hash not stable") + } +} + +func BenchmarkMarshalHashPermStat(b *testing.B) { + v := PermStat{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalHash() + } +} + +func BenchmarkAppendMsgPermStat(b *testing.B) { + v := PermStat{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalHash() + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalHash() + } +} + func TestMarshalHashProviderProfile(t *testing.T) { v := ProviderProfile{} binary.Read(rand.Reader, binary.BigEndian, &v) From b1d0c9cc263dd3d3bb726efc9a66a4bcfe46e2df Mon Sep 17 00:00:00 2001 From: Qi Xiao Date: Tue, 8 Jan 2019 15:15:50 +0800 Subject: [PATCH 05/10] Refactor Read/Write permission into Read/Write/ReadWrite and using flag bit --- blockproducer/errors.go | 4 +- blockproducer/metastate.go | 26 ++-- blockproducer/metastate_test.go | 4 +- types/account.go | 92 +++++++++----- types/account_test.go | 25 ++-- worker/dbms_test.go | 212 ++++++++++++++++++++++---------- 6 files changed, 236 insertions(+), 127 deletions(-) diff --git a/blockproducer/errors.go b/blockproducer/errors.go index 42421b072..2a6cc74cb 100644 --- a/blockproducer/errors.go +++ b/blockproducer/errors.go @@ -60,8 +60,8 @@ var ( ErrNoEnoughMiner = errors.New("can not get enough miners") // ErrAccountPermissionDeny indicates that the sender does not own admin permission to the sqlchain. ErrAccountPermissionDeny = errors.New("account permission deny") - // ErrNoAdminLeft indicates there is no admin user in sqlchain. - ErrNoAdminLeft = errors.New("no admin user left") + // ErrNoSuperUserLeft indicates there is no super user in sqlchain. + ErrNoSuperUserLeft = errors.New("no super user left") // ErrInvalidPermission indicates that the permission is invalid. ErrInvalidPermission = errors.New("invalid permission") // ErrMinerUserNotMatch indicates that the miner and user do not match. diff --git a/blockproducer/metastate.go b/blockproducer/metastate.go index 0390e832e..68db687c3 100644 --- a/blockproducer/metastate.go +++ b/blockproducer/metastate.go @@ -893,21 +893,21 @@ func (s *metaState) updatePermission(tx *types.UpdatePermission) (err error) { return ErrInvalidPermission } - // check whether sender is admin and find targetUser - isAdmin := false - numOfAdmin := 0 + // check whether sender has super privilege and find targetUser + isSuperUser := false + numOfSuperUsers := 0 targetUserIndex := -1 for i, u := range so.Users { - isAdmin = isAdmin || (sender == u.Address && u.Permission.HasAdminPermission()) - if u.Permission.HasAdminPermission() { - numOfAdmin++ + isSuperUser = isSuperUser || (sender == u.Address && u.Permission.HasSuperPermission()) + if u.Permission.HasSuperPermission() { + numOfSuperUsers++ } if tx.TargetUser == u.Address { targetUserIndex = i } } - if !isAdmin { + if !isSuperUser { log.WithFields(log.Fields{ "sender": sender, "dbID": tx.TargetSQLChain, @@ -916,8 +916,8 @@ func (s *metaState) updatePermission(tx *types.UpdatePermission) (err error) { } // return error if number of Admin <= 1 and Admin want to revoke permission of itself - if numOfAdmin <= 1 && tx.TargetUser == sender && !tx.Permission.HasAdminPermission() { - err = ErrNoAdminLeft + if numOfSuperUsers <= 1 && tx.TargetUser == sender && !tx.Permission.HasSuperPermission() { + err = ErrNoSuperUserLeft log.WithFields(log.Fields{ "sender": sender, "dbID": tx.TargetSQLChain, @@ -952,14 +952,14 @@ func (s *metaState) updateKeys(tx *types.IssueKeys) (err error) { } // check sender's permission - isAdmin := false + isSuperUser := false for _, user := range so.Users { - if sender == user.Address && user.Permission.HasAdminPermission() { - isAdmin = true + if sender == user.Address && user.Permission.HasSuperPermission() { + isSuperUser = true break } } - if !isAdmin { + if !isSuperUser { log.WithFields(log.Fields{ "sender": sender, "dbID": tx.TargetSQLChain, diff --git a/blockproducer/metastate_test.go b/blockproducer/metastate_test.go index aedd69e87..21fe2ae02 100644 --- a/blockproducer/metastate_test.go +++ b/blockproducer/metastate_test.go @@ -1000,7 +1000,7 @@ func TestMetaState(t *testing.T) { So(err, ShouldBeNil) err = ms.apply(&up) So(errors.Cause(err), ShouldEqual, ErrDatabaseNotFound) - up.Permission = types.UserPermissionFromRole(types.NumberOfUserPermission) + up.Permission = types.UserPermissionFromRole(types.Void) up.TargetSQLChain = dbAccount err = up.Sign(privKey1) So(err, ShouldBeNil) @@ -1039,7 +1039,7 @@ func TestMetaState(t *testing.T) { err = up.Sign(privKey3) So(err, ShouldBeNil) err = ms.apply(&up) - So(errors.Cause(err), ShouldEqual, ErrNoAdminLeft) + So(errors.Cause(err), ShouldEqual, ErrNoSuperUserLeft) // addr1(read) update addr3(admin) fail up.Nonce = cd1.Nonce + 2 err = up.Sign(privKey1) diff --git a/types/account.go b/types/account.go index dce6b1449..b0eac76b1 100644 --- a/types/account.go +++ b/types/account.go @@ -18,6 +18,7 @@ package types import ( "encoding/json" + "strings" "sync" pi "github.com/CovenantSQL/CovenantSQL/blockproducer/interfaces" @@ -56,16 +57,24 @@ type UserPermission struct { } const ( - // Void defines the initial permission. - Void UserPermissionRole = iota - // Admin defines the admin user permission. - Admin + // Read defines the read user permission. + Read UserPermissionRole = 1 << iota // Write defines the writer user permission. Write - // Read defines the reader user permission. - Read - // NumberOfUserPermission defines the user permission number. - NumberOfUserPermission + // Super defines the super user permission. + Super + + // ReadOnly defines the reader user permission. + ReadOnly = Read + // WriteOnly defines the writer user permission. + WriteOnly = Write + // ReadWrite defines the reader && writer user permission. + ReadWrite = Read | Write + // Admin defines the privilege to full control the database. + Admin = Read | Write | Super + + // Void defines the initial permission. + Void UserPermissionRole = 0 ) // UnmarshalJSON implements the json.Unmarshler interface. @@ -85,33 +94,48 @@ func (r UserPermissionRole) MarshalJSON() ([]byte, error) { // String implements the fmt.Stringer interface. func (r UserPermissionRole) String() string { - switch r { - case Admin: - return "Admin" - case Write: - return "Write" - case Read: - return "Read" - case Void: + if r == Void { return "Void" - default: - return "Unknown" + } else if r == Admin { + return "Admin" + } + + var res []string + if r&Read != 0 { + res = append(res, "Read") + } + if r&Write != 0 { + res = append(res, "Write") + } + if r&Super != 0 { + res = append(res, "Super") } + + return strings.Join(res, ",") } // FromString converts string to UserPermissionRole. func (r *UserPermissionRole) FromString(perm string) { - switch perm { - case "Admin": - *r = Admin - case "Write": - *r = Write - case "Read": - *r = Read - case "Void": + if perm == "Void" { *r = Void - default: - *r = NumberOfUserPermission + return + } else if perm == "Admin" { + *r = Admin + return + } + + *r = Void + + for _, p := range strings.Split(perm, ",") { + p = strings.TrimSpace(p) + switch p { + case "Read": + *r |= Read + case "Write": + *r |= Write + case "Super": + *r |= Super + } } } @@ -127,7 +151,7 @@ func (up *UserPermission) HasReadPermission() bool { if up == nil { return false } - return up.Role >= Admin && up.Role < NumberOfUserPermission + return up.Role&Read != 0 } // HasWritePermission returns true if user owns write permission. @@ -135,20 +159,20 @@ func (up *UserPermission) HasWritePermission() bool { if up == nil { return false } - return up.Role >= Admin && up.Role <= Write + return up.Role&Write != 0 } -// HasAdminPermission returns true if user owns admin permission. -func (up *UserPermission) HasAdminPermission() bool { +// HasSuperPermission returns true if user owns super permission. +func (up *UserPermission) HasSuperPermission() bool { if up == nil { return false } - return up.Role == Admin + return up.Role&Super != 0 } // IsValid returns whether the permission object is valid or not. func (up *UserPermission) IsValid() bool { - return up != nil && up.Role < NumberOfUserPermission && up.Role >= Admin + return up != nil && up.Role != 0 } // HasDisallowedQueryPatterns returns whether the queries are permitted. diff --git a/types/account_test.go b/types/account_test.go index 7a7eb665e..828930869 100644 --- a/types/account_test.go +++ b/types/account_test.go @@ -33,12 +33,17 @@ func TestUserPermissionFromRole(t *testing.T) { err = json.Unmarshal([]byte(`"Write"`), &r) So(err, ShouldBeNil) So(r, ShouldEqual, Write) + err = json.Unmarshal([]byte(`"Read,Write"`), &r) + So(err, ShouldBeNil) + So(r, ShouldEqual, ReadWrite) }) Convey("test string/from string", t, func() { var r UserPermissionRole So(r, ShouldEqual, Void) r.FromString(Read.String()) So(r, ShouldEqual, Read) + r.FromString(ReadWrite.String()) + So(r, ShouldEqual, ReadWrite) }) } @@ -47,7 +52,7 @@ func TestUserPermission(t *testing.T) { p := (*UserPermission)(nil) So(p.HasReadPermission(), ShouldBeFalse) So(p.HasWritePermission(), ShouldBeFalse) - So(p.HasAdminPermission(), ShouldBeFalse) + So(p.HasSuperPermission(), ShouldBeFalse) So(p.IsValid(), ShouldBeFalse) _, state := p.HasDisallowedQueryPatterns([]Query{}) So(state, ShouldBeTrue) @@ -55,30 +60,30 @@ func TestUserPermission(t *testing.T) { Convey("has read permission", t, func() { So(UserPermissionFromRole(Void).HasReadPermission(), ShouldBeFalse) So(UserPermissionFromRole(Read).HasReadPermission(), ShouldBeTrue) - So(UserPermissionFromRole(Write).HasReadPermission(), ShouldBeTrue) + So(UserPermissionFromRole(Write).HasReadPermission(), ShouldBeFalse) + So(UserPermissionFromRole(ReadWrite).HasReadPermission(), ShouldBeTrue) So(UserPermissionFromRole(Admin).HasReadPermission(), ShouldBeTrue) - So(UserPermissionFromRole(NumberOfUserPermission).HasReadPermission(), ShouldBeFalse) }) Convey("has write permission", t, func() { So(UserPermissionFromRole(Void).HasWritePermission(), ShouldBeFalse) So(UserPermissionFromRole(Read).HasWritePermission(), ShouldBeFalse) So(UserPermissionFromRole(Write).HasWritePermission(), ShouldBeTrue) + So(UserPermissionFromRole(ReadWrite).HasWritePermission(), ShouldBeTrue) So(UserPermissionFromRole(Admin).HasWritePermission(), ShouldBeTrue) - So(UserPermissionFromRole(NumberOfUserPermission).HasWritePermission(), ShouldBeFalse) }) Convey("has admin permission", t, func() { - So(UserPermissionFromRole(Void).HasAdminPermission(), ShouldBeFalse) - So(UserPermissionFromRole(Read).HasAdminPermission(), ShouldBeFalse) - So(UserPermissionFromRole(Write).HasAdminPermission(), ShouldBeFalse) - So(UserPermissionFromRole(Admin).HasAdminPermission(), ShouldBeTrue) - So(UserPermissionFromRole(NumberOfUserPermission).HasAdminPermission(), ShouldBeFalse) + So(UserPermissionFromRole(Void).HasSuperPermission(), ShouldBeFalse) + So(UserPermissionFromRole(Read).HasSuperPermission(), ShouldBeFalse) + So(UserPermissionFromRole(Write).HasSuperPermission(), ShouldBeFalse) + So(UserPermissionFromRole(ReadWrite).HasSuperPermission(), ShouldBeFalse) + So(UserPermissionFromRole(Admin).HasSuperPermission(), ShouldBeTrue) }) Convey("is valid", t, func() { So(UserPermissionFromRole(Void).IsValid(), ShouldBeFalse) So(UserPermissionFromRole(Read).IsValid(), ShouldBeTrue) So(UserPermissionFromRole(Write).IsValid(), ShouldBeTrue) + So(UserPermissionFromRole(ReadWrite).IsValid(), ShouldBeTrue) So(UserPermissionFromRole(Admin).IsValid(), ShouldBeTrue) - So(UserPermissionFromRole(NumberOfUserPermission).IsValid(), ShouldBeFalse) }) Convey("query patterns", t, func() { // empty patterns limitation diff --git a/worker/dbms_test.go b/worker/dbms_test.go index db8d80cbe..92309ca6b 100644 --- a/worker/dbms_test.go +++ b/worker/dbms_test.go @@ -19,6 +19,7 @@ package worker import ( "io/ioutil" "os" + "sync/atomic" "testing" "time" @@ -103,6 +104,8 @@ func TestDBMS(t *testing.T) { err = req.Sign(privateKey) So(err, ShouldBeNil) + var seqNo uint64 + Convey("with bp privilege", func() { // send update again err = testRequest(route.DBSDeploy, req, &res) @@ -112,10 +115,12 @@ func TestDBMS(t *testing.T) { // sending write query var writeQuery *types.Request var queryRes *types.Response - writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, 1, 1, dbID, []string{ - "create table test (test int)", - "insert into test values(1)", - }) + writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "create table test (test int)", + "insert into test values(1)", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, writeQuery, &queryRes) @@ -123,9 +128,11 @@ func TestDBMS(t *testing.T) { // sending read query var readQuery *types.Request - readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, 1, 2, dbID, []string{ - "select * from test", - }) + readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "select * from test", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, readQuery, &queryRes) @@ -134,22 +141,24 @@ func TestDBMS(t *testing.T) { // grant write and read permission err = dbms.UpdatePermission(dbAddr.DatabaseID(), userAddr, - &types.PermStat{Permission: types.UserPermissionFromRole(types.Write), Status: types.Normal}) + &types.PermStat{Permission: types.UserPermissionFromRole(types.ReadWrite), Status: types.Normal}) So(err, ShouldBeNil) userState, ok := dbms.busService.RequestPermStat(dbAddr.DatabaseID(), userAddr) So(ok, ShouldBeTrue) So(userState.Permission, ShouldNotBeNil) - So(userState.Permission.Role, ShouldEqual, types.Write) + So(userState.Permission.Role, ShouldEqual, types.ReadWrite) So(userState.Status, ShouldEqual, types.Normal) Convey("success write and read", func() { // sending write query var writeQuery *types.Request var queryRes *types.Response - writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, 1, 1, dbID, []string{ - "create table test (test int)", - "insert into test values(1)", - }) + writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "create table test (test int)", + "insert into test values(1)", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, writeQuery, &queryRes) @@ -160,9 +169,11 @@ func TestDBMS(t *testing.T) { // sending read query var readQuery *types.Request - readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, 1, 2, dbID, []string{ - "select * from test", - }) + readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "select * from test", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, readQuery, &queryRes) @@ -205,20 +216,24 @@ func TestDBMS(t *testing.T) { // sending write query var writeQuery *types.Request var queryRes *types.Response - writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, 1, 3, dbID, []string{ - "create table test (test int)", - "insert into test values(1)", - }) + writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "insert into test values(1)", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, writeQuery, &queryRes) + So(err, ShouldNotBeNil) So(err.Error(), ShouldContainSubstring, ErrPermissionDeny.Error()) // sending read query var readQuery *types.Request - readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, 1, 4, dbID, []string{ - "select * from test", - }) + readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "select * from test", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, readQuery, &queryRes) @@ -227,6 +242,46 @@ func TestDBMS(t *testing.T) { err = dbms.addTxSubscription(dbID, nodeID, 1) So(err, ShouldBeNil) }) + + // grant write only permission + err = dbms.UpdatePermission(dbAddr.DatabaseID(), userAddr, + &types.PermStat{Permission: types.UserPermissionFromRole(types.Write), Status: types.Normal}) + userState, ok = dbms.busService.RequestPermStat(dbAddr.DatabaseID(), userAddr) + So(ok, ShouldBeTrue) + So(userState.Permission, ShouldNotBeNil) + So(userState.Permission.Role, ShouldEqual, types.Write) + So(userState.Status, ShouldEqual, types.Normal) + + Convey("success writing and failed to read", func() { + // sending read query + var readQuery *types.Request + var queryRes *types.Response + readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "select * from test", + }) + So(err, ShouldBeNil) + + err = testRequest(route.DBSQuery, readQuery, &queryRes) + So(err, ShouldNotBeNil) + So(err.Error(), ShouldContainSubstring, ErrPermissionDeny.Error()) + + // sending write query + var writeQuery *types.Request + writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "insert into test values(1)", + }) + So(err, ShouldBeNil) + + err = testRequest(route.DBSQuery, writeQuery, &queryRes) + So(err, ShouldBeNil) + err = queryRes.Verify() + So(err, ShouldBeNil) + So(queryRes.Header.RowCount, ShouldEqual, 0) + }) }) // grant invalid permission @@ -243,10 +298,12 @@ func TestDBMS(t *testing.T) { // sending write query var writeQuery *types.Request var queryRes *types.Response - writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, 1, 5, dbID, []string{ - "create table test (test int)", - "insert into test values(1)", - }) + writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "create table test (test int)", + "insert into test values(1)", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, writeQuery, &queryRes) @@ -254,9 +311,11 @@ func TestDBMS(t *testing.T) { // sending read query var readQuery *types.Request - readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, 1, 6, dbID, []string{ - "select * from test", - }) + readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "select * from test", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, readQuery, &queryRes) @@ -280,10 +339,12 @@ func TestDBMS(t *testing.T) { // sending write query var writeQuery *types.Request var queryRes *types.Response - writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, 1, 7, dbID, []string{ - "create table test (test int)", - "insert into test values(1)", - }) + writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "create table test (test int)", + "insert into test values(1)", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, writeQuery, &queryRes) @@ -291,9 +352,11 @@ func TestDBMS(t *testing.T) { // sending read query var readQuery *types.Request - readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, 1, 8, dbID, []string{ - "select * from test", - }) + readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "select * from test", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, readQuery, &queryRes) @@ -314,10 +377,12 @@ func TestDBMS(t *testing.T) { // sending write query var writeQuery *types.Request var queryRes *types.Response - writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, 1, 9, dbID, []string{ - "create table test (test int)", - "insert into test values(1)", - }) + writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "create table test (test int)", + "insert into test values(1)", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, writeQuery, &queryRes) @@ -328,9 +393,11 @@ func TestDBMS(t *testing.T) { // sending read query var readQuery *types.Request - readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, 1, 10, dbID, []string{ - "select * from test", - }) + readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "select * from test", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, readQuery, &queryRes) @@ -376,10 +443,12 @@ func TestDBMS(t *testing.T) { var queryRes *types.Response // sending allowed write query - writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, 1, 11, dbID, []string{ - "create table test (test int)", - "INSERT INTO TEST VALUES(1)", - }) + writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "create table test (test int)", + "INSERT INTO TEST VALUES(1)", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, writeQuery, &queryRes) @@ -390,9 +459,11 @@ func TestDBMS(t *testing.T) { // sending allowed read query var readQuery *types.Request - readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, 1, 12, dbID, []string{ - "SELECT 1", - }) + readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "SELECT 1", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, readQuery, &queryRes) @@ -405,35 +476,43 @@ func TestDBMS(t *testing.T) { So(queryRes.Payload.Rows[0].Values[0], ShouldEqual, 1) // sending disallowed write query - writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, 1, 13, dbID, []string{ - "insert into test values(1)", - }) + writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "insert into test values(1)", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, writeQuery, &queryRes) So(err, ShouldNotBeNil) // sending disallowed write query mixed with valid write query - writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, 1, 14, dbID, []string{ - "INSERT INTO TEST VALUES(1)", - "insert into test values(1)", - }) + writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "INSERT INTO TEST VALUES(1)", + "insert into test values(1)", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, writeQuery, &queryRes) So(err, ShouldNotBeNil) // sending disallowed read query - readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, 1, 15, dbID, []string{ - "select * from test", - }) + readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "select * from test", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, readQuery, &queryRes) So(err, ShouldNotBeNil) // sending disallowed read query - readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, 1, 16, dbID, []string{ - "SELECT 1", - "select * from test", - }) + readQuery, err = buildQueryWithDatabaseID(types.ReadQuery, + 1, atomic.AddUint64(&seqNo, 1), + dbID, []string{ + "SELECT 1", + "select * from test", + }) So(err, ShouldBeNil) err = testRequest(route.DBSQuery, readQuery, &queryRes) So(err, ShouldNotBeNil) @@ -453,7 +532,8 @@ func TestDBMS(t *testing.T) { // sending write query var writeQuery *types.Request var queryRes *types.Response - writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, 1, 1, + writeQuery, err = buildQueryWithDatabaseID(types.WriteQuery, + 1, atomic.AddUint64(&seqNo, 1), proto.DatabaseID("db_not_exists"), []string{ "create table test (test int)", "insert into test values(1)", From 147a9abe6a0a3b82f9155249cde73411742b0f6d Mon Sep 17 00:00:00 2001 From: Qi Xiao Date: Mon, 28 Jan 2019 16:26:08 +0800 Subject: [PATCH 06/10] Fix permission compatibility issues --- worker/dbms.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worker/dbms.go b/worker/dbms.go index 028025b84..b6d3e59e9 100644 --- a/worker/dbms.go +++ b/worker/dbms.go @@ -469,7 +469,7 @@ func (dbms *DBMS) Ack(ack *types.Ack) (err error) { if err != nil { return } - err = dbms.checkPermission(addr, ack.Header.Response.Request.DatabaseID, types.ReadQuery) + err = dbms.checkPermission(addr, ack.Header.Response.Request.DatabaseID, types.ReadQuery, nil) if err != nil { return } From 93308c3d9fca337fc38f532df2f0b678567dd1b9 Mon Sep 17 00:00:00 2001 From: Qi Xiao Date: Tue, 29 Jan 2019 15:52:15 +0800 Subject: [PATCH 07/10] Simplify super user check logic in metastate --- blockproducer/metastate.go | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/blockproducer/metastate.go b/blockproducer/metastate.go index 68db687c3..79e19abe9 100644 --- a/blockproducer/metastate.go +++ b/blockproducer/metastate.go @@ -894,11 +894,16 @@ func (s *metaState) updatePermission(tx *types.UpdatePermission) (err error) { } // check whether sender has super privilege and find targetUser - isSuperUser := false numOfSuperUsers := 0 targetUserIndex := -1 for i, u := range so.Users { - isSuperUser = isSuperUser || (sender == u.Address && u.Permission.HasSuperPermission()) + if sender == u.Address && !u.Permission.HasSuperPermission() { + log.WithFields(log.Fields{ + "sender": sender, + "dbID": tx.TargetSQLChain, + }).WithError(ErrAccountPermissionDeny).Error("unexpected error in updatePermission") + return ErrAccountPermissionDeny + } if u.Permission.HasSuperPermission() { numOfSuperUsers++ } @@ -907,14 +912,6 @@ func (s *metaState) updatePermission(tx *types.UpdatePermission) (err error) { } } - if !isSuperUser { - log.WithFields(log.Fields{ - "sender": sender, - "dbID": tx.TargetSQLChain, - }).WithError(ErrAccountPermissionDeny).Error("unexpected error in updatePermission") - return ErrAccountPermissionDeny - } - // return error if number of Admin <= 1 and Admin want to revoke permission of itself if numOfSuperUsers <= 1 && tx.TargetUser == sender && !tx.Permission.HasSuperPermission() { err = ErrNoSuperUserLeft @@ -952,20 +949,19 @@ func (s *metaState) updateKeys(tx *types.IssueKeys) (err error) { } // check sender's permission - isSuperUser := false for _, user := range so.Users { - if sender == user.Address && user.Permission.HasSuperPermission() { - isSuperUser = true + if sender == user.Address { + if !user.Permission.HasSuperPermission() { + log.WithFields(log.Fields{ + "sender": sender, + "dbID": tx.TargetSQLChain, + }).WithError(ErrAccountPermissionDeny).Error("unexpected error in updateKeys") + return ErrAccountPermissionDeny + } + break } } - if !isSuperUser { - log.WithFields(log.Fields{ - "sender": sender, - "dbID": tx.TargetSQLChain, - }).WithError(ErrAccountPermissionDeny).Error("unexpected error in updateKeys") - return ErrAccountPermissionDeny - } // update miner's key keyMap := make(map[proto.AccountAddress]string) From c018bc588a3e78d558557d882517ed476703215f Mon Sep 17 00:00:00 2001 From: Qi Xiao Date: Tue, 29 Jan 2019 16:04:51 +0800 Subject: [PATCH 08/10] Update account MarshalHash --- types/account_gen.go | 32 -------------------------------- types/account_gen_test.go | 37 ------------------------------------- 2 files changed, 69 deletions(-) diff --git a/types/account_gen.go b/types/account_gen.go index 2807344b4..320d3ebd0 100644 --- a/types/account_gen.go +++ b/types/account_gen.go @@ -89,38 +89,6 @@ func (z *MinerInfo) Msgsize() (s int) { return } -// MarshalHash marshals for hash -func (z *PermStat) MarshalHash() (o []byte, err error) { - var b []byte - o = hsp.Require(b, z.Msgsize()) - // map header, size 2 - o = append(o, 0x82, 0x82) - if z.Permission == nil { - o = hsp.AppendNil(o) - } else { - if oTemp, err := z.Permission.MarshalHash(); err != nil { - return nil, err - } else { - o = hsp.AppendBytes(o, oTemp) - } - } - o = append(o, 0x82) - o = hsp.AppendInt32(o, int32(z.Status)) - return -} - -// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *PermStat) Msgsize() (s int) { - s = 1 + 11 - if z.Permission == nil { - s += hsp.NilSize - } else { - s += z.Permission.Msgsize() - } - s += 7 + hsp.Int32Size - return -} - // MarshalHash marshals for hash func (z *ProviderProfile) MarshalHash() (o []byte, err error) { var b []byte diff --git a/types/account_gen_test.go b/types/account_gen_test.go index 9b6a8a5d3..388a19ddb 100644 --- a/types/account_gen_test.go +++ b/types/account_gen_test.go @@ -83,43 +83,6 @@ func BenchmarkAppendMsgMinerInfo(b *testing.B) { } } -func TestMarshalHashPermStat(t *testing.T) { - v := PermStat{} - binary.Read(rand.Reader, binary.BigEndian, &v) - bts1, err := v.MarshalHash() - if err != nil { - t.Fatal(err) - } - bts2, err := v.MarshalHash() - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(bts1, bts2) { - t.Fatal("hash not stable") - } -} - -func BenchmarkMarshalHashPermStat(b *testing.B) { - v := PermStat{} - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - v.MarshalHash() - } -} - -func BenchmarkAppendMsgPermStat(b *testing.B) { - v := PermStat{} - bts := make([]byte, 0, v.Msgsize()) - bts, _ = v.MarshalHash() - b.SetBytes(int64(len(bts))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - bts, _ = v.MarshalHash() - } -} - func TestMarshalHashProviderProfile(t *testing.T) { v := ProviderProfile{} binary.Read(rand.Reader, binary.BigEndian, &v) From a52981f3f836a72815d3efedcb2da63f9637b602 Mon Sep 17 00:00:00 2001 From: Qi Xiao Date: Tue, 29 Jan 2019 14:38:59 +0800 Subject: [PATCH 09/10] Fix block producer irreversible block test --- cmd/cqld/cqld_test.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cmd/cqld/cqld_test.go b/cmd/cqld/cqld_test.go index bbadb0ae3..0e193eee9 100644 --- a/cmd/cqld/cqld_test.go +++ b/cmd/cqld/cqld_test.go @@ -27,6 +27,7 @@ import ( bp "github.com/CovenantSQL/CovenantSQL/blockproducer" "github.com/CovenantSQL/CovenantSQL/conf" "github.com/CovenantSQL/CovenantSQL/crypto/kms" + "github.com/CovenantSQL/CovenantSQL/proto" "github.com/CovenantSQL/CovenantSQL/route" "github.com/CovenantSQL/CovenantSQL/rpc" "github.com/CovenantSQL/CovenantSQL/types" @@ -67,13 +68,18 @@ func TestCQLD(t *testing.T) { // Wait for block producing time.Sleep(15 * time.Second) - // Kill one BP + // Kill one BP follower err = nodeCmds[2].Cmd.Process.Signal(syscall.SIGTERM) So(err, ShouldBeNil) time.Sleep(15 * time.Second) // set current bp to leader bp - rpc.SetCurrentBP(route.GetBPs()[0]) + for _, n := range conf.GConf.KnownNodes { + if n.Role == proto.Leader { + rpc.SetCurrentBP(n.ID) + break + } + } // The other peers should be waiting var ( From a67c156e9a309a69bd513aa925b8c03201468bc7 Mon Sep 17 00:00:00 2001 From: Qi Xiao Date: Tue, 29 Jan 2019 14:22:02 +0800 Subject: [PATCH 10/10] Temporary disable query cancel test case --- cmd/cql-minerd/integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/cql-minerd/integration_test.go b/cmd/cql-minerd/integration_test.go index 8d7351f82..fc6f8f5bc 100644 --- a/cmd/cql-minerd/integration_test.go +++ b/cmd/cql-minerd/integration_test.go @@ -490,7 +490,7 @@ func TestFullProcess(t *testing.T) { So(err, ShouldBeNil) So(resultBytes, ShouldResemble, []byte("ha\001ppy")) - Convey("test query cancel", FailureContinues, func(c C) { + SkipConvey("test query cancel", FailureContinues, func(c C) { /* test cancel write query */ wg := sync.WaitGroup{} wg.Add(1)