From 429fdcf0b90a565a55c9ba6b4d8fd09689676fd4 Mon Sep 17 00:00:00 2001 From: John Shahid Date: Mon, 21 Oct 2013 17:48:00 -0400 Subject: [PATCH] add read/write access information to the user structure. --- src/coordinator/user.go | 88 +++++++++++++++++++++++++++++ src/coordinator/user_test.go | 16 ++++++ src/protocol/protocol.pb.go | 50 ++++++++++++++-- src/protocol/protocol.proto | 9 +++ src/protocol/protocol_extensions.go | 17 ++++++ 5 files changed, 175 insertions(+), 5 deletions(-) diff --git a/src/coordinator/user.go b/src/coordinator/user.go index ffe664301e..c84d9acfaa 100644 --- a/src/coordinator/user.go +++ b/src/coordinator/user.go @@ -100,12 +100,100 @@ func (self *User) RemoveDbAdmin(u *User, db string) error { return nil } +func (self *User) AddReadMatcher(u *User, m *protocol.Matcher) error { + var err error + u.u.ReadFrom, err = self.addMatcher(u, m, u.u.ReadFrom) + return err +} + +func (self *User) RemoveReadMatcher(u *User, m *protocol.Matcher) error { + var err error + u.u.ReadFrom, err = self.removeMatcher(u, m, u.u.ReadFrom) + return err +} + +func (self *User) AddWriteMatcher(u *User, m *protocol.Matcher) error { + var err error + u.u.WriteTo, err = self.addMatcher(u, m, u.u.WriteTo) + return err +} + +func (self *User) RemoveWriteMatcher(u *User, m *protocol.Matcher) error { + var err error + u.u.WriteTo, err = self.removeMatcher(u, m, u.u.WriteTo) + return err +} + +func (self *User) HasWriteAccess(name string) bool { + for _, matcher := range self.u.WriteTo { + if matcher.Matches(name) { + return true + } + } + + return false +} + +func (self *User) HasReadAccess(name string) bool { + for _, matcher := range self.u.ReadFrom { + if matcher.Matches(name) { + return true + } + } + + return false +} + // private funcs +func (self *User) addMatcher(u *User, m *protocol.Matcher, matchers []*protocol.Matcher) ([]*protocol.Matcher, error) { + if err := self.getMatcherPermission(m); err != nil { + return matchers, err + } + + idx := u.findMatcher(m, matchers) + if idx == -1 { + matchers = append(matchers, m) + } + return matchers, nil +} + +func (self *User) removeMatcher(u *User, m *protocol.Matcher, matchers []*protocol.Matcher) ([]*protocol.Matcher, error) { + if err := self.getMatcherPermission(m); err != nil { + return matchers, err + } + + idx := u.findMatcher(m, matchers) + if idx != -1 { + matchers = append(matchers[:idx], matchers[idx+1:]...) + } + return matchers, nil +} + +func (self *User) getMatcherPermission(m *protocol.Matcher) error { + if !self.IsClusterAdmin() && m.GetIsRegex() { + return fmt.Errorf("Only a cluster admin can add access to a regex") + } + + if !self.IsClusterAdmin() && !self.IsDbAdmin(m.GetName()) { + return fmt.Errorf("Cannot add permission to a db your not an admin of") + } + return nil +} + func (self *User) isValidPwd(password string) bool { return bcrypt.CompareHashAndPassword([]byte(self.u.GetHash()), []byte(password)) == nil } +func (self *User) findMatcher(matcher *protocol.Matcher, matchers []*protocol.Matcher) int { + for idx, m := range matchers { + if m.GetIsRegex() == matcher.GetIsRegex() && m.GetName() == matcher.GetName() { + return idx + } + } + return -1 +} + func hashPassword(password string) ([]byte, error) { // The second arg is the cost of the hashing, higher is slower but makes it harder // to brute force, since it will be really slow and impractical diff --git a/src/coordinator/user_test.go b/src/coordinator/user_test.go index c7a1cddc59..f9c5e3945f 100644 --- a/src/coordinator/user_test.go +++ b/src/coordinator/user_test.go @@ -54,6 +54,14 @@ func (self *UserSuite) TestClusterAdminUser(c *C) { c.Assert(u.IsDbAdmin("db2"), Equals, false) c.Assert(root.RemoveDbAdmin(u, "db1"), IsNil) c.Assert(u.IsDbAdmin("db1"), Equals, false) + + // can add read and write keys that are regex + c.Assert(root.AddReadMatcher(u, protocol.NewMatcher(false, "db1")), IsNil) + c.Assert(root.AddReadMatcher(u, protocol.NewMatcher(true, "db2.*")), IsNil) + c.Assert(u.HasReadAccess("db1"), Equals, true) + c.Assert(u.HasReadAccess("db2.foobar"), Equals, true) + c.Assert(root.RemoveReadMatcher(u, protocol.NewMatcher(false, "db1")), IsNil) + c.Assert(u.HasReadAccess("db1"), Equals, false) } func (self *UserSuite) TestDbAdminUser(c *C) { @@ -89,6 +97,14 @@ func (self *UserSuite) TestDbAdminUser(c *C) { c.Assert(dbAdmin.RemoveDbAdmin(u, "db1"), IsNil) c.Assert(u.IsDbAdmin("db1"), Equals, false) c.Assert(u.IsDbAdmin("db2"), Equals, false) + + // can add read access to the db that he's administering only + c.Assert(dbAdmin.AddReadMatcher(u, protocol.NewMatcher(false, "db1")), IsNil) + c.Assert(dbAdmin.AddReadMatcher(u, protocol.NewMatcher(false, "db2")), NotNil) + c.Assert(dbAdmin.AddReadMatcher(u, protocol.NewMatcher(true, "db2.*")), NotNil) + c.Assert(u.HasReadAccess("db1"), Equals, true) + c.Assert(u.HasReadAccess("db2"), Equals, false) + c.Assert(u.HasReadAccess("db2.foobar"), Equals, false) } func (self *UserSuite) BenchmarkHashing(c *C) { diff --git a/src/protocol/protocol.pb.go b/src/protocol/protocol.pb.go index 1478511f68..18ebe0fa88 100644 --- a/src/protocol/protocol.pb.go +++ b/src/protocol/protocol.pb.go @@ -317,12 +317,38 @@ func (m *Response) GetServers() []string { return nil } +type Matcher struct { + IsRegex *bool `protobuf:"varint,1,req,name=is_regex" json:"is_regex,omitempty"` + Name *string `protobuf:"bytes,2,req,name=name" json:"name,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Matcher) Reset() { *m = Matcher{} } +func (m *Matcher) String() string { return proto.CompactTextString(m) } +func (*Matcher) ProtoMessage() {} + +func (m *Matcher) GetIsRegex() bool { + if m != nil && m.IsRegex != nil { + return *m.IsRegex + } + return false +} + +func (m *Matcher) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + type User struct { - Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"` - Hash *string `protobuf:"bytes,2,req,name=hash" json:"hash,omitempty"` - ClusterAdmin *bool `protobuf:"varint,3,req,name=clusterAdmin" json:"clusterAdmin,omitempty"` - AdminFor []string `protobuf:"bytes,4,rep,name=admin_for" json:"admin_for,omitempty"` - XXX_unrecognized []byte `json:"-"` + Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"` + Hash *string `protobuf:"bytes,2,req,name=hash" json:"hash,omitempty"` + ClusterAdmin *bool `protobuf:"varint,3,req,name=clusterAdmin" json:"clusterAdmin,omitempty"` + AdminFor []string `protobuf:"bytes,4,rep,name=admin_for" json:"admin_for,omitempty"` + ReadFrom []*Matcher `protobuf:"bytes,5,rep,name=read_from" json:"read_from,omitempty"` + WriteTo []*Matcher `protobuf:"bytes,6,rep,name=write_to" json:"write_to,omitempty"` + XXX_unrecognized []byte `json:"-"` } func (m *User) Reset() { *m = User{} } @@ -357,6 +383,20 @@ func (m *User) GetAdminFor() []string { return nil } +func (m *User) GetReadFrom() []*Matcher { + if m != nil { + return m.ReadFrom + } + return nil +} + +func (m *User) GetWriteTo() []*Matcher { + if m != nil { + return m.WriteTo + } + return nil +} + func init() { proto.RegisterEnum("protocol.FieldDefinition_Type", FieldDefinition_Type_name, FieldDefinition_Type_value) proto.RegisterEnum("protocol.Request_Type", Request_Type_name, Request_Type_value) diff --git a/src/protocol/protocol.proto b/src/protocol/protocol.proto index 314958b4ec..3b30907919 100644 --- a/src/protocol/protocol.proto +++ b/src/protocol/protocol.proto @@ -51,11 +51,20 @@ message Response { repeated string servers = 3; } +message Matcher { + required bool is_regex = 1; + required string name = 2; +} + message User { required string name = 1; required string hash = 2; required bool clusterAdmin = 3; // the list of dbs that the user is admin for repeated string admin_for = 4; + // the list of dbs that the user can read from + repeated Matcher read_from = 5; + // the list of dbs that the user can write to + repeated Matcher write_to = 6; } diff --git a/src/protocol/protocol_extensions.go b/src/protocol/protocol_extensions.go index 2af31b8aa5..0a3377dbe1 100644 --- a/src/protocol/protocol_extensions.go +++ b/src/protocol/protocol_extensions.go @@ -2,6 +2,7 @@ package protocol import ( "code.google.com/p/goprotobuf/proto" + "regexp" ) func UnmarshalPoint(data []byte) (point *Point, err error) { @@ -21,3 +22,19 @@ func (self *Point) GetTimestampInMicroseconds() *int64 { func (self *Point) SetTimestampInMicroseconds(t int64) { self.Timestamp = &t } + +func NewMatcher(isRegex bool, name string) *Matcher { + return &Matcher{ + Name: &name, + IsRegex: &isRegex, + } +} + +func (self *Matcher) Matches(name string) bool { + if self.GetIsRegex() { + matches, _ := regexp.MatchString(self.GetName(), name) + return matches + } + + return self.GetName() == name +}