add read/write access information to the user structure.

pull/17/head
John Shahid 2013-10-21 17:48:00 -04:00
parent b4851b712e
commit 429fdcf0b9
5 changed files with 175 additions and 5 deletions

View File

@ -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

View File

@ -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) {

View File

@ -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)

View File

@ -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;
}

View File

@ -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
}