From 8e9037e0819f840df03c92e994afce56c8f4139a Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Fri, 3 Nov 2017 14:21:21 -0400 Subject: [PATCH 01/11] Add Role to chronograf.Source --- bolt/internal/internal.go | 2 + bolt/internal/internal.pb.go | 698 +++++++++++++++++++++++++++++++---- bolt/internal/internal.proto | 1 + bolt/sources_test.go | 3 + chronograf.go | 1 + 5 files changed, 630 insertions(+), 75 deletions(-) diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index fadb5f517..57a9c9ab5 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -24,6 +24,7 @@ func MarshalSource(s chronograf.Source) ([]byte, error) { Default: s.Default, Telegraf: s.Telegraf, Organization: s.Organization, + Role: s.Role, }) } @@ -46,6 +47,7 @@ func UnmarshalSource(data []byte, s *chronograf.Source) error { s.Default = pb.Default s.Telegraf = pb.Telegraf s.Organization = pb.Organization + s.Role = pb.Role return nil } diff --git a/bolt/internal/internal.pb.go b/bolt/internal/internal.pb.go index 44bbb5ae0..8b6c466b8 100644 --- a/bolt/internal/internal.pb.go +++ b/bolt/internal/internal.pb.go @@ -56,6 +56,7 @@ type Source struct { MetaURL string `protobuf:"bytes,10,opt,name=MetaURL,proto3" json:"MetaURL,omitempty"` SharedSecret string `protobuf:"bytes,11,opt,name=SharedSecret,proto3" json:"SharedSecret,omitempty"` Organization string `protobuf:"bytes,12,opt,name=Organization,proto3" json:"Organization,omitempty"` + Role string `protobuf:"bytes,13,opt,name=Role,proto3" json:"Role,omitempty"` } func (m *Source) Reset() { *m = Source{} } @@ -63,6 +64,97 @@ func (m *Source) String() string { return proto.CompactTextString(m) func (*Source) ProtoMessage() {} func (*Source) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{0} } +func (m *Source) GetID() int64 { + if m != nil { + return m.ID + } + return 0 +} + +func (m *Source) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Source) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +func (m *Source) GetUsername() string { + if m != nil { + return m.Username + } + return "" +} + +func (m *Source) GetPassword() string { + if m != nil { + return m.Password + } + return "" +} + +func (m *Source) GetURL() string { + if m != nil { + return m.URL + } + return "" +} + +func (m *Source) GetDefault() bool { + if m != nil { + return m.Default + } + return false +} + +func (m *Source) GetTelegraf() string { + if m != nil { + return m.Telegraf + } + return "" +} + +func (m *Source) GetInsecureSkipVerify() bool { + if m != nil { + return m.InsecureSkipVerify + } + return false +} + +func (m *Source) GetMetaURL() string { + if m != nil { + return m.MetaURL + } + return "" +} + +func (m *Source) GetSharedSecret() string { + if m != nil { + return m.SharedSecret + } + return "" +} + +func (m *Source) GetOrganization() string { + if m != nil { + return m.Organization + } + return "" +} + +func (m *Source) GetRole() string { + if m != nil { + return m.Role + } + return "" +} + type Dashboard struct { ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` @@ -76,6 +168,20 @@ func (m *Dashboard) String() string { return proto.CompactTextString( func (*Dashboard) ProtoMessage() {} func (*Dashboard) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{1} } +func (m *Dashboard) GetID() int64 { + if m != nil { + return m.ID + } + return 0 +} + +func (m *Dashboard) GetName() string { + if m != nil { + return m.Name + } + return "" +} + func (m *Dashboard) GetCells() []*DashboardCell { if m != nil { return m.Cells @@ -90,6 +196,13 @@ func (m *Dashboard) GetTemplates() []*Template { return nil } +func (m *Dashboard) GetOrganization() string { + if m != nil { + return m.Organization + } + return "" +} + type DashboardCell struct { X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"` Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,omitempty"` @@ -107,6 +220,34 @@ func (m *DashboardCell) String() string { return proto.CompactTextStr func (*DashboardCell) ProtoMessage() {} func (*DashboardCell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{2} } +func (m *DashboardCell) GetX() int32 { + if m != nil { + return m.X + } + return 0 +} + +func (m *DashboardCell) GetY() int32 { + if m != nil { + return m.Y + } + return 0 +} + +func (m *DashboardCell) GetW() int32 { + if m != nil { + return m.W + } + return 0 +} + +func (m *DashboardCell) GetH() int32 { + if m != nil { + return m.H + } + return 0 +} + func (m *DashboardCell) GetQueries() []*Query { if m != nil { return m.Queries @@ -114,6 +255,27 @@ func (m *DashboardCell) GetQueries() []*Query { return nil } +func (m *DashboardCell) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *DashboardCell) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +func (m *DashboardCell) GetID() string { + if m != nil { + return m.ID + } + return "" +} + func (m *DashboardCell) GetAxes() map[string]*Axis { if m != nil { return m.Axes @@ -122,7 +284,7 @@ func (m *DashboardCell) GetAxes() map[string]*Axis { } type Axis struct { - LegacyBounds []int64 `protobuf:"varint,1,rep,name=legacyBounds" json:"legacyBounds,omitempty"` + LegacyBounds []int64 `protobuf:"varint,1,rep,packed,name=legacyBounds" json:"legacyBounds,omitempty"` Bounds []string `protobuf:"bytes,2,rep,name=bounds" json:"bounds,omitempty"` Label string `protobuf:"bytes,3,opt,name=label,proto3" json:"label,omitempty"` Prefix string `protobuf:"bytes,4,opt,name=prefix,proto3" json:"prefix,omitempty"` @@ -136,6 +298,55 @@ func (m *Axis) String() string { return proto.CompactTextString(m) } func (*Axis) ProtoMessage() {} func (*Axis) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} } +func (m *Axis) GetLegacyBounds() []int64 { + if m != nil { + return m.LegacyBounds + } + return nil +} + +func (m *Axis) GetBounds() []string { + if m != nil { + return m.Bounds + } + return nil +} + +func (m *Axis) GetLabel() string { + if m != nil { + return m.Label + } + return "" +} + +func (m *Axis) GetPrefix() string { + if m != nil { + return m.Prefix + } + return "" +} + +func (m *Axis) GetSuffix() string { + if m != nil { + return m.Suffix + } + return "" +} + +func (m *Axis) GetBase() string { + if m != nil { + return m.Base + } + return "" +} + +func (m *Axis) GetScale() string { + if m != nil { + return m.Scale + } + return "" +} + type Template struct { ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` TempVar string `protobuf:"bytes,2,opt,name=temp_var,json=tempVar,proto3" json:"temp_var,omitempty"` @@ -150,6 +361,20 @@ func (m *Template) String() string { return proto.CompactTextString(m func (*Template) ProtoMessage() {} func (*Template) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} } +func (m *Template) GetID() string { + if m != nil { + return m.ID + } + return "" +} + +func (m *Template) GetTempVar() string { + if m != nil { + return m.TempVar + } + return "" +} + func (m *Template) GetValues() []*TemplateValue { if m != nil { return m.Values @@ -157,6 +382,20 @@ func (m *Template) GetValues() []*TemplateValue { return nil } +func (m *Template) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +func (m *Template) GetLabel() string { + if m != nil { + return m.Label + } + return "" +} + func (m *Template) GetQuery() *TemplateQuery { if m != nil { return m.Query @@ -175,6 +414,27 @@ func (m *TemplateValue) String() string { return proto.CompactTextStr func (*TemplateValue) ProtoMessage() {} func (*TemplateValue) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} } +func (m *TemplateValue) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +func (m *TemplateValue) GetValue() string { + if m != nil { + return m.Value + } + return "" +} + +func (m *TemplateValue) GetSelected() bool { + if m != nil { + return m.Selected + } + return false +} + type TemplateQuery struct { Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"` Db string `protobuf:"bytes,2,opt,name=db,proto3" json:"db,omitempty"` @@ -189,6 +449,48 @@ func (m *TemplateQuery) String() string { return proto.CompactTextStr func (*TemplateQuery) ProtoMessage() {} func (*TemplateQuery) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} } +func (m *TemplateQuery) GetCommand() string { + if m != nil { + return m.Command + } + return "" +} + +func (m *TemplateQuery) GetDb() string { + if m != nil { + return m.Db + } + return "" +} + +func (m *TemplateQuery) GetRp() string { + if m != nil { + return m.Rp + } + return "" +} + +func (m *TemplateQuery) GetMeasurement() string { + if m != nil { + return m.Measurement + } + return "" +} + +func (m *TemplateQuery) GetTagKey() string { + if m != nil { + return m.TagKey + } + return "" +} + +func (m *TemplateQuery) GetFieldKey() string { + if m != nil { + return m.FieldKey + } + return "" +} + type Server struct { ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` @@ -205,13 +507,69 @@ func (m *Server) String() string { return proto.CompactTextString(m) func (*Server) ProtoMessage() {} func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} } +func (m *Server) GetID() int64 { + if m != nil { + return m.ID + } + return 0 +} + +func (m *Server) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Server) GetUsername() string { + if m != nil { + return m.Username + } + return "" +} + +func (m *Server) GetPassword() string { + if m != nil { + return m.Password + } + return "" +} + +func (m *Server) GetURL() string { + if m != nil { + return m.URL + } + return "" +} + +func (m *Server) GetSrcID() int64 { + if m != nil { + return m.SrcID + } + return 0 +} + +func (m *Server) GetActive() bool { + if m != nil { + return m.Active + } + return false +} + +func (m *Server) GetOrganization() string { + if m != nil { + return m.Organization + } + return "" +} + type Layout struct { ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` Application string `protobuf:"bytes,2,opt,name=Application,proto3" json:"Application,omitempty"` Measurement string `protobuf:"bytes,3,opt,name=Measurement,proto3" json:"Measurement,omitempty"` Cells []*Cell `protobuf:"bytes,4,rep,name=Cells" json:"Cells,omitempty"` Autoflow bool `protobuf:"varint,5,opt,name=Autoflow,proto3" json:"Autoflow,omitempty"` - Organization string `protobuf:"bytes,7,opt,name=Organization,proto3" json:"Organization,omitempty"` + Organization string `protobuf:"bytes,6,opt,name=Organization,proto3" json:"Organization,omitempty"` } func (m *Layout) Reset() { *m = Layout{} } @@ -219,6 +577,27 @@ func (m *Layout) String() string { return proto.CompactTextString(m) func (*Layout) ProtoMessage() {} func (*Layout) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} } +func (m *Layout) GetID() string { + if m != nil { + return m.ID + } + return "" +} + +func (m *Layout) GetApplication() string { + if m != nil { + return m.Application + } + return "" +} + +func (m *Layout) GetMeasurement() string { + if m != nil { + return m.Measurement + } + return "" +} + func (m *Layout) GetCells() []*Cell { if m != nil { return m.Cells @@ -226,6 +605,20 @@ func (m *Layout) GetCells() []*Cell { return nil } +func (m *Layout) GetAutoflow() bool { + if m != nil { + return m.Autoflow + } + return false +} + +func (m *Layout) GetOrganization() string { + if m != nil { + return m.Organization + } + return "" +} + type Cell struct { X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"` Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,omitempty"` @@ -234,7 +627,7 @@ type Cell struct { Queries []*Query `protobuf:"bytes,5,rep,name=queries" json:"queries,omitempty"` I string `protobuf:"bytes,6,opt,name=i,proto3" json:"i,omitempty"` Name string `protobuf:"bytes,7,opt,name=name,proto3" json:"name,omitempty"` - Yranges []int64 `protobuf:"varint,8,rep,name=yranges" json:"yranges,omitempty"` + Yranges []int64 `protobuf:"varint,8,rep,packed,name=yranges" json:"yranges,omitempty"` Ylabels []string `protobuf:"bytes,9,rep,name=ylabels" json:"ylabels,omitempty"` Type string `protobuf:"bytes,10,opt,name=type,proto3" json:"type,omitempty"` Axes map[string]*Axis `protobuf:"bytes,11,rep,name=axes" json:"axes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value"` @@ -245,6 +638,34 @@ func (m *Cell) String() string { return proto.CompactTextString(m) } func (*Cell) ProtoMessage() {} func (*Cell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{9} } +func (m *Cell) GetX() int32 { + if m != nil { + return m.X + } + return 0 +} + +func (m *Cell) GetY() int32 { + if m != nil { + return m.Y + } + return 0 +} + +func (m *Cell) GetW() int32 { + if m != nil { + return m.W + } + return 0 +} + +func (m *Cell) GetH() int32 { + if m != nil { + return m.H + } + return 0 +} + func (m *Cell) GetQueries() []*Query { if m != nil { return m.Queries @@ -252,6 +673,41 @@ func (m *Cell) GetQueries() []*Query { return nil } +func (m *Cell) GetI() string { + if m != nil { + return m.I + } + return "" +} + +func (m *Cell) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Cell) GetYranges() []int64 { + if m != nil { + return m.Yranges + } + return nil +} + +func (m *Cell) GetYlabels() []string { + if m != nil { + return m.Ylabels + } + return nil +} + +func (m *Cell) GetType() string { + if m != nil { + return m.Type + } + return "" +} + func (m *Cell) GetAxes() map[string]*Axis { if m != nil { return m.Axes @@ -275,6 +731,48 @@ func (m *Query) String() string { return proto.CompactTextString(m) } func (*Query) ProtoMessage() {} func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{10} } +func (m *Query) GetCommand() string { + if m != nil { + return m.Command + } + return "" +} + +func (m *Query) GetDB() string { + if m != nil { + return m.DB + } + return "" +} + +func (m *Query) GetRP() string { + if m != nil { + return m.RP + } + return "" +} + +func (m *Query) GetGroupBys() []string { + if m != nil { + return m.GroupBys + } + return nil +} + +func (m *Query) GetWheres() []string { + if m != nil { + return m.Wheres + } + return nil +} + +func (m *Query) GetLabel() string { + if m != nil { + return m.Label + } + return "" +} + func (m *Query) GetRange() *Range { if m != nil { return m.Range @@ -282,6 +780,13 @@ func (m *Query) GetRange() *Range { return nil } +func (m *Query) GetSource() string { + if m != nil { + return m.Source + } + return "" +} + type Range struct { Upper int64 `protobuf:"varint,1,opt,name=Upper,proto3" json:"Upper,omitempty"` Lower int64 `protobuf:"varint,2,opt,name=Lower,proto3" json:"Lower,omitempty"` @@ -292,6 +797,20 @@ func (m *Range) String() string { return proto.CompactTextString(m) } func (*Range) ProtoMessage() {} func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{11} } +func (m *Range) GetUpper() int64 { + if m != nil { + return m.Upper + } + return 0 +} + +func (m *Range) GetLower() int64 { + if m != nil { + return m.Lower + } + return 0 +} + type AlertRule struct { ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` JSON string `protobuf:"bytes,2,opt,name=JSON,proto3" json:"JSON,omitempty"` @@ -304,6 +823,34 @@ func (m *AlertRule) String() string { return proto.CompactTextString( func (*AlertRule) ProtoMessage() {} func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{12} } +func (m *AlertRule) GetID() string { + if m != nil { + return m.ID + } + return "" +} + +func (m *AlertRule) GetJSON() string { + if m != nil { + return m.JSON + } + return "" +} + +func (m *AlertRule) GetSrcID() int64 { + if m != nil { + return m.SrcID + } + return 0 +} + +func (m *AlertRule) GetKapaID() int64 { + if m != nil { + return m.KapaID + } + return 0 +} + type User struct { ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` @@ -438,76 +985,77 @@ func init() { func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } var fileDescriptorInternal = []byte{ - // 1133 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0xcf, 0x8f, 0xdb, 0x44, - 0x14, 0x96, 0x63, 0x3b, 0x89, 0x5f, 0xb6, 0x0b, 0x1a, 0x55, 0xd4, 0x14, 0x09, 0x05, 0x0b, 0xa4, - 0x20, 0xd1, 0x05, 0xb5, 0x42, 0x42, 0x1c, 0x90, 0xb2, 0x1b, 0x54, 0x2d, 0xbb, 0x6d, 0x97, 0xc9, - 0xee, 0x72, 0x42, 0xd5, 0xc4, 0x79, 0x49, 0xac, 0x3a, 0xb1, 0x19, 0xdb, 0xbb, 0x31, 0xff, 0x0d, - 0x12, 0x27, 0x8e, 0x88, 0x3b, 0x12, 0x27, 0xd4, 0x23, 0x7f, 0x11, 0x7a, 0x33, 0x63, 0xc7, 0x21, - 0xa1, 0xda, 0x0b, 0xdc, 0xe6, 0x7b, 0x6f, 0xfc, 0xe6, 0xfd, 0xfa, 0x3e, 0x19, 0x0e, 0xa3, 0x55, - 0x8e, 0x72, 0x25, 0xe2, 0xa3, 0x54, 0x26, 0x79, 0xc2, 0xba, 0x15, 0x0e, 0xfe, 0x6a, 0x41, 0x7b, - 0x9c, 0x14, 0x32, 0x44, 0x76, 0x08, 0xad, 0xd3, 0x91, 0x6f, 0xf5, 0xad, 0x81, 0xcd, 0x5b, 0xa7, - 0x23, 0xc6, 0xc0, 0x79, 0x2e, 0x96, 0xe8, 0xb7, 0xfa, 0xd6, 0xc0, 0xe3, 0xea, 0x4c, 0xb6, 0xcb, - 0x32, 0x45, 0xdf, 0xd6, 0x36, 0x3a, 0xb3, 0x87, 0xd0, 0xbd, 0xca, 0x28, 0xda, 0x12, 0x7d, 0x47, - 0xd9, 0x6b, 0x4c, 0xbe, 0x0b, 0x91, 0x65, 0xb7, 0x89, 0x9c, 0xfa, 0xae, 0xf6, 0x55, 0x98, 0xbd, - 0x0d, 0xf6, 0x15, 0x3f, 0xf7, 0xdb, 0xca, 0x4c, 0x47, 0xe6, 0x43, 0x67, 0x84, 0x33, 0x51, 0xc4, - 0xb9, 0xdf, 0xe9, 0x5b, 0x83, 0x2e, 0xaf, 0x20, 0xc5, 0xb9, 0xc4, 0x18, 0xe7, 0x52, 0xcc, 0xfc, - 0xae, 0x8e, 0x53, 0x61, 0x76, 0x04, 0xec, 0x74, 0x95, 0x61, 0x58, 0x48, 0x1c, 0xbf, 0x8a, 0xd2, - 0x6b, 0x94, 0xd1, 0xac, 0xf4, 0x3d, 0x15, 0x60, 0x8f, 0x87, 0x5e, 0x79, 0x86, 0xb9, 0xa0, 0xb7, - 0x41, 0x85, 0xaa, 0x20, 0x0b, 0xe0, 0x60, 0xbc, 0x10, 0x12, 0xa7, 0x63, 0x0c, 0x25, 0xe6, 0x7e, - 0x4f, 0xb9, 0xb7, 0x6c, 0x74, 0xe7, 0x85, 0x9c, 0x8b, 0x55, 0xf4, 0xa3, 0xc8, 0xa3, 0x64, 0xe5, - 0x1f, 0xe8, 0x3b, 0x4d, 0x5b, 0xf0, 0x9b, 0x05, 0xde, 0x48, 0x64, 0x8b, 0x49, 0x22, 0xe4, 0xf4, - 0x4e, 0x7d, 0x7d, 0x04, 0x6e, 0x88, 0x71, 0x9c, 0xf9, 0x76, 0xdf, 0x1e, 0xf4, 0x1e, 0x3f, 0x38, - 0xaa, 0x07, 0x56, 0xc7, 0x39, 0xc1, 0x38, 0xe6, 0xfa, 0x16, 0xfb, 0x0c, 0xbc, 0x1c, 0x97, 0x69, - 0x2c, 0x72, 0xcc, 0x7c, 0x47, 0x7d, 0xc2, 0x36, 0x9f, 0x5c, 0x1a, 0x17, 0xdf, 0x5c, 0xda, 0x49, - 0xdb, 0xdd, 0x93, 0xf6, 0x2f, 0x2d, 0xb8, 0xb7, 0xf5, 0x1c, 0x3b, 0x00, 0x6b, 0xad, 0x32, 0x77, - 0xb9, 0xb5, 0x26, 0x54, 0xaa, 0xac, 0x5d, 0x6e, 0x95, 0x84, 0x6e, 0xd5, 0x1e, 0xb8, 0xdc, 0xba, - 0x25, 0xb4, 0x50, 0xd3, 0x77, 0xb9, 0xb5, 0x60, 0x1f, 0x43, 0xe7, 0x87, 0x02, 0x65, 0x84, 0x99, - 0xef, 0xaa, 0xec, 0xde, 0xda, 0x64, 0xf7, 0x6d, 0x81, 0xb2, 0xe4, 0x95, 0x9f, 0xba, 0xa1, 0x36, - 0x47, 0xaf, 0x81, 0x3a, 0x93, 0x2d, 0xa7, 0x2d, 0xeb, 0x68, 0x1b, 0x9d, 0x4d, 0x17, 0xf5, 0xec, - 0xa9, 0x8b, 0x9f, 0x83, 0x23, 0xd6, 0x98, 0xf9, 0x9e, 0x8a, 0xff, 0xc1, 0xbf, 0x34, 0xec, 0x68, - 0xb8, 0xc6, 0xec, 0xeb, 0x55, 0x2e, 0x4b, 0xae, 0xae, 0x3f, 0x7c, 0x0a, 0x5e, 0x6d, 0xa2, 0x0d, - 0x7c, 0x85, 0xa5, 0x2a, 0xd0, 0xe3, 0x74, 0x64, 0x1f, 0x82, 0x7b, 0x23, 0xe2, 0x42, 0x0f, 0xa7, - 0xf7, 0xf8, 0x70, 0x13, 0x76, 0xb8, 0x8e, 0x32, 0xae, 0x9d, 0x5f, 0xb6, 0xbe, 0xb0, 0x82, 0x5f, - 0x2d, 0x70, 0xc8, 0x46, 0x9d, 0x8d, 0x71, 0x2e, 0xc2, 0xf2, 0x38, 0x29, 0x56, 0xd3, 0xcc, 0xb7, - 0xfa, 0xf6, 0xc0, 0xe6, 0x5b, 0x36, 0xf6, 0x0e, 0xb4, 0x27, 0xda, 0xdb, 0xea, 0xdb, 0x03, 0x8f, - 0x1b, 0xc4, 0xee, 0x83, 0x1b, 0x8b, 0x09, 0xc6, 0x86, 0x4f, 0x1a, 0xd0, 0xed, 0x54, 0xe2, 0x2c, - 0x5a, 0x1b, 0x3a, 0x19, 0x44, 0xf6, 0xac, 0x98, 0x91, 0x5d, 0x4f, 0xcf, 0x20, 0x6a, 0xd7, 0x44, - 0x64, 0x75, 0x0b, 0xe9, 0x4c, 0x91, 0xb3, 0x50, 0xc4, 0x55, 0x0f, 0x35, 0x08, 0x7e, 0xb7, 0x88, - 0x47, 0x7a, 0x27, 0x1a, 0x7b, 0xa9, 0x3b, 0xfa, 0x2e, 0x74, 0x69, 0x5f, 0x5e, 0xde, 0x08, 0x69, - 0x76, 0xb3, 0x43, 0xf8, 0x5a, 0x48, 0xf6, 0x29, 0xb4, 0x55, 0xe5, 0x7b, 0xf6, 0xb3, 0x0a, 0x77, - 0x4d, 0x7e, 0x6e, 0xae, 0xd5, 0x13, 0x74, 0x1a, 0x13, 0xac, 0x8b, 0x75, 0x9b, 0xc5, 0x3e, 0x02, - 0x97, 0x56, 0xa1, 0x54, 0xd9, 0xef, 0x8d, 0xac, 0x17, 0x46, 0xdf, 0x0a, 0xae, 0xe0, 0xde, 0xd6, - 0x8b, 0xf5, 0x4b, 0xd6, 0xf6, 0x4b, 0x9b, 0x29, 0x7a, 0x66, 0x6a, 0xa4, 0x21, 0x19, 0xc6, 0x18, - 0xe6, 0x38, 0x55, 0xfd, 0xee, 0xf2, 0x1a, 0x07, 0x3f, 0x59, 0x9b, 0xb8, 0xea, 0x3d, 0x52, 0x89, - 0x30, 0x59, 0x2e, 0xc5, 0x6a, 0x6a, 0x42, 0x57, 0x90, 0xfa, 0x36, 0x9d, 0x98, 0xd0, 0xad, 0xe9, - 0x84, 0xb0, 0x4c, 0xcd, 0x04, 0x5b, 0x32, 0x65, 0x7d, 0xe8, 0x2d, 0x51, 0x64, 0x85, 0xc4, 0x25, - 0xae, 0x72, 0xd3, 0x82, 0xa6, 0x89, 0x3d, 0x80, 0x4e, 0x2e, 0xe6, 0x2f, 0x69, 0xf7, 0xcc, 0x24, - 0x73, 0x31, 0x3f, 0xc3, 0x92, 0xbd, 0x07, 0xde, 0x2c, 0xc2, 0x78, 0xaa, 0x5c, 0x7a, 0x9c, 0x5d, - 0x65, 0x38, 0xc3, 0x32, 0xf8, 0xd3, 0x82, 0xf6, 0x18, 0xe5, 0x0d, 0xca, 0x3b, 0x49, 0x4a, 0x53, - 0x96, 0xed, 0x37, 0xc8, 0xb2, 0xb3, 0x5f, 0x96, 0xdd, 0x8d, 0x2c, 0xdf, 0x07, 0x77, 0x2c, 0xc3, - 0xd3, 0x91, 0xca, 0xc8, 0xe6, 0x1a, 0xd0, 0x36, 0x0e, 0xc3, 0x3c, 0xba, 0x41, 0xa3, 0xd5, 0x06, - 0xed, 0x28, 0x4d, 0x77, 0x8f, 0xd2, 0xfc, 0x61, 0x41, 0xfb, 0x5c, 0x94, 0x49, 0x91, 0xef, 0x6c, - 0x61, 0x1f, 0x7a, 0xc3, 0x34, 0x8d, 0xa3, 0x50, 0x7f, 0xad, 0x2b, 0x6a, 0x9a, 0xe8, 0xc6, 0xb3, - 0x46, 0x7f, 0x75, 0x6d, 0x4d, 0x13, 0xb1, 0xf8, 0x44, 0xa9, 0xa9, 0x96, 0xc6, 0x06, 0x8b, 0xb5, - 0x88, 0x2a, 0x27, 0x35, 0x61, 0x58, 0xe4, 0xc9, 0x2c, 0x4e, 0x6e, 0x55, 0xb5, 0x5d, 0x5e, 0xe3, - 0x9d, 0x22, 0xda, 0x7b, 0x8a, 0x78, 0xdd, 0x02, 0xe7, 0xff, 0x52, 0xc9, 0x03, 0xb0, 0x22, 0x93, - 0x84, 0x15, 0xd5, 0x9a, 0xd9, 0x69, 0x68, 0xa6, 0x0f, 0x9d, 0x52, 0x8a, 0xd5, 0x1c, 0x33, 0xbf, - 0xab, 0x14, 0xa8, 0x82, 0xca, 0xa3, 0xb8, 0xa6, 0xc5, 0xd2, 0xe3, 0x15, 0xac, 0xb9, 0x03, 0x0d, - 0xee, 0x7c, 0x62, 0x74, 0xb5, 0xa7, 0x32, 0xf2, 0xb7, 0x5b, 0xf7, 0xdf, 0xc9, 0xe9, 0x6b, 0x0b, - 0xdc, 0x9a, 0x78, 0x27, 0xdb, 0xc4, 0x3b, 0xd9, 0x10, 0x6f, 0x74, 0x5c, 0x11, 0x6f, 0x74, 0x4c, - 0x98, 0x5f, 0x54, 0xc4, 0xe3, 0x17, 0x34, 0xd0, 0xa7, 0x32, 0x29, 0xd2, 0xe3, 0x52, 0x4f, 0xde, - 0xe3, 0x35, 0xa6, 0x6d, 0xfd, 0x6e, 0x81, 0xd2, 0xb4, 0xda, 0xe3, 0x06, 0xd1, 0x6e, 0x9f, 0x2b, - 0x51, 0xd2, 0xcd, 0xd5, 0x80, 0x7d, 0x04, 0x2e, 0xa7, 0xe6, 0xa9, 0x0e, 0x6f, 0xcd, 0x45, 0x99, - 0xb9, 0xf6, 0x52, 0x50, 0xfd, 0xef, 0x64, 0x96, 0xdc, 0xa0, 0xe0, 0x89, 0xf9, 0x9c, 0xa2, 0x5f, - 0xa5, 0x29, 0x4a, 0x43, 0x55, 0x0d, 0xd4, 0x9b, 0xc9, 0x2d, 0x6a, 0x95, 0xb5, 0xb9, 0x06, 0xc1, - 0xf7, 0xe0, 0x0d, 0x63, 0x94, 0x39, 0x2f, 0xe2, 0x5d, 0x6d, 0x66, 0xe0, 0x7c, 0x33, 0x7e, 0xf1, - 0xbc, 0x22, 0x38, 0x9d, 0x37, 0xb4, 0xb4, 0xff, 0x41, 0xcb, 0x33, 0x91, 0x8a, 0xd3, 0x91, 0xda, - 0x33, 0x9b, 0x1b, 0x14, 0xfc, 0x6c, 0x81, 0x43, 0xfc, 0x6f, 0x84, 0x76, 0xde, 0xa4, 0x1d, 0x17, - 0x32, 0xb9, 0x89, 0xa6, 0x28, 0x2b, 0xed, 0xa8, 0xb0, 0x2a, 0x3a, 0x5c, 0x60, 0xfd, 0xb3, 0x67, - 0x10, 0xcd, 0x9a, 0x27, 0x71, 0xbd, 0xcb, 0x8d, 0x59, 0x93, 0x99, 0x6b, 0x27, 0x7b, 0x1f, 0x60, - 0x5c, 0xa4, 0x28, 0x87, 0xd3, 0x65, 0xa4, 0x69, 0xd5, 0xe5, 0x0d, 0x4b, 0xf0, 0x15, 0x38, 0x74, - 0x71, 0x87, 0x80, 0xd6, 0x2e, 0x01, 0xf7, 0x65, 0x1e, 0x5c, 0x6e, 0x7f, 0x77, 0xa7, 0x6a, 0xfb, - 0xd0, 0x33, 0xff, 0x99, 0xf4, 0x74, 0x25, 0x28, 0x0d, 0xd3, 0xa4, 0xad, 0x7e, 0x9b, 0x9f, 0xfc, - 0x1d, 0x00, 0x00, 0xff, 0xff, 0x65, 0x9d, 0x67, 0xe0, 0x48, 0x0b, 0x00, 0x00, + // 1148 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0xcf, 0x8e, 0xe3, 0xc4, + 0x13, 0x56, 0xc7, 0x71, 0x12, 0x57, 0x66, 0xf7, 0xf7, 0x53, 0x6b, 0xc5, 0x9a, 0x45, 0x42, 0xc1, + 0x02, 0x29, 0x48, 0xec, 0x80, 0x76, 0x85, 0x84, 0x38, 0x20, 0x65, 0x26, 0x68, 0x35, 0xec, 0xbf, + 0xa1, 0x33, 0xb3, 0x9c, 0xd0, 0xaa, 0xe3, 0x54, 0x12, 0x6b, 0x9d, 0xd8, 0xb4, 0xed, 0x99, 0x98, + 0xb7, 0x41, 0xe2, 0xc4, 0x11, 0x71, 0x47, 0xe2, 0x84, 0xf6, 0x41, 0x78, 0x0e, 0x54, 0xdd, 0x6d, + 0xc7, 0xd9, 0x84, 0xd5, 0x5c, 0xe0, 0xd6, 0x5f, 0x55, 0x77, 0x75, 0x57, 0xd5, 0x57, 0x9f, 0x1a, + 0x6e, 0x47, 0xeb, 0x1c, 0xd5, 0x5a, 0xc6, 0xc7, 0xa9, 0x4a, 0xf2, 0x84, 0xf7, 0x2a, 0x1c, 0xfc, + 0xd5, 0x82, 0xce, 0x24, 0x29, 0x54, 0x88, 0xfc, 0x36, 0xb4, 0xce, 0xc6, 0x3e, 0x1b, 0xb0, 0xa1, + 0x23, 0x5a, 0x67, 0x63, 0xce, 0xa1, 0xfd, 0x4c, 0xae, 0xd0, 0x6f, 0x0d, 0xd8, 0xd0, 0x13, 0x7a, + 0x4d, 0xb6, 0x8b, 0x32, 0x45, 0xdf, 0x31, 0x36, 0x5a, 0xf3, 0x7b, 0xd0, 0xbb, 0xcc, 0x28, 0xda, + 0x0a, 0xfd, 0xb6, 0xb6, 0xd7, 0x98, 0x7c, 0xe7, 0x32, 0xcb, 0xae, 0x13, 0x35, 0xf3, 0x5d, 0xe3, + 0xab, 0x30, 0xff, 0x3f, 0x38, 0x97, 0xe2, 0x89, 0xdf, 0xd1, 0x66, 0x5a, 0x72, 0x1f, 0xba, 0x63, + 0x9c, 0xcb, 0x22, 0xce, 0xfd, 0xee, 0x80, 0x0d, 0x7b, 0xa2, 0x82, 0x14, 0xe7, 0x02, 0x63, 0x5c, + 0x28, 0x39, 0xf7, 0x7b, 0x26, 0x4e, 0x85, 0xf9, 0x31, 0xf0, 0xb3, 0x75, 0x86, 0x61, 0xa1, 0x70, + 0xf2, 0x2a, 0x4a, 0x5f, 0xa0, 0x8a, 0xe6, 0xa5, 0xef, 0xe9, 0x00, 0x07, 0x3c, 0x74, 0xcb, 0x53, + 0xcc, 0x25, 0xdd, 0x0d, 0x3a, 0x54, 0x05, 0x79, 0x00, 0x47, 0x93, 0xa5, 0x54, 0x38, 0x9b, 0x60, + 0xa8, 0x30, 0xf7, 0xfb, 0xda, 0xbd, 0x63, 0xa3, 0x3d, 0xcf, 0xd5, 0x42, 0xae, 0xa3, 0x1f, 0x65, + 0x1e, 0x25, 0x6b, 0xff, 0xc8, 0xec, 0x69, 0xda, 0xa8, 0x4a, 0x22, 0x89, 0xd1, 0xbf, 0x65, 0xaa, + 0x44, 0xeb, 0xe0, 0x37, 0x06, 0xde, 0x58, 0x66, 0xcb, 0x69, 0x22, 0xd5, 0xec, 0x46, 0xb5, 0xbe, + 0x0f, 0x6e, 0x88, 0x71, 0x9c, 0xf9, 0xce, 0xc0, 0x19, 0xf6, 0x1f, 0xdc, 0x3d, 0xae, 0x9b, 0x58, + 0xc7, 0x39, 0xc5, 0x38, 0x16, 0x66, 0x17, 0xff, 0x0c, 0xbc, 0x1c, 0x57, 0x69, 0x2c, 0x73, 0xcc, + 0xfc, 0xb6, 0x3e, 0xc2, 0xb7, 0x47, 0x2e, 0xac, 0x4b, 0x6c, 0x37, 0xed, 0xa5, 0xe2, 0xee, 0xa7, + 0x12, 0xfc, 0xd2, 0x82, 0x5b, 0x3b, 0xd7, 0xf1, 0x23, 0x60, 0x1b, 0xfd, 0x72, 0x57, 0xb0, 0x0d, + 0xa1, 0x52, 0xbf, 0xda, 0x15, 0xac, 0x24, 0x74, 0xad, 0xb9, 0xe1, 0x0a, 0x76, 0x4d, 0x68, 0xa9, + 0x19, 0xe1, 0x0a, 0xb6, 0xe4, 0x1f, 0x43, 0xf7, 0x87, 0x02, 0x55, 0x84, 0x99, 0xef, 0xea, 0xd7, + 0xfd, 0x6f, 0xfb, 0xba, 0x6f, 0x0b, 0x54, 0xa5, 0xa8, 0xfc, 0x54, 0x0d, 0xcd, 0x26, 0x43, 0x0d, + 0xbd, 0x26, 0x5b, 0x4e, 0xcc, 0xeb, 0x1a, 0x1b, 0xad, 0x6d, 0x15, 0x0d, 0x1f, 0xa8, 0x8a, 0x9f, + 0x43, 0x5b, 0x6e, 0x30, 0xf3, 0x3d, 0x1d, 0xff, 0x83, 0x7f, 0x28, 0xd8, 0xf1, 0x68, 0x83, 0xd9, + 0xd7, 0xeb, 0x5c, 0x95, 0x42, 0x6f, 0xbf, 0xf7, 0x08, 0xbc, 0xda, 0x44, 0xac, 0x7c, 0x85, 0xa5, + 0x4e, 0xd0, 0x13, 0xb4, 0xe4, 0x1f, 0x82, 0x7b, 0x25, 0xe3, 0xc2, 0x34, 0xa7, 0xff, 0xe0, 0xf6, + 0x36, 0xec, 0x68, 0x13, 0x65, 0xc2, 0x38, 0xbf, 0x6c, 0x7d, 0xc1, 0x82, 0x5f, 0x19, 0xb4, 0xc9, + 0x46, 0x95, 0x8d, 0x71, 0x21, 0xc3, 0xf2, 0x24, 0x29, 0xd6, 0xb3, 0xcc, 0x67, 0x03, 0x67, 0xe8, + 0x88, 0x1d, 0x1b, 0x7f, 0x07, 0x3a, 0x53, 0xe3, 0x6d, 0x0d, 0x9c, 0xa1, 0x27, 0x2c, 0xe2, 0x77, + 0xc0, 0x8d, 0xe5, 0x14, 0x63, 0x3b, 0x63, 0x06, 0xd0, 0xee, 0x54, 0xe1, 0x3c, 0xda, 0xd8, 0x11, + 0xb3, 0x88, 0xec, 0x59, 0x31, 0x27, 0xbb, 0xe9, 0x9e, 0x45, 0x54, 0xae, 0xa9, 0xcc, 0xea, 0x12, + 0xd2, 0x9a, 0x22, 0x67, 0xa1, 0x8c, 0xab, 0x1a, 0x1a, 0x10, 0xfc, 0xce, 0x68, 0xb6, 0x0c, 0x27, + 0x1a, 0xbc, 0x34, 0x15, 0x7d, 0x17, 0x7a, 0xc4, 0x97, 0x97, 0x57, 0x52, 0x59, 0x6e, 0x76, 0x09, + 0xbf, 0x90, 0x8a, 0x7f, 0x0a, 0x1d, 0x9d, 0xf9, 0x01, 0x7e, 0x56, 0xe1, 0x5e, 0x90, 0x5f, 0xd8, + 0x6d, 0x75, 0x07, 0xdb, 0x8d, 0x0e, 0xd6, 0xc9, 0xba, 0xcd, 0x64, 0xef, 0x83, 0x4b, 0x54, 0x28, + 0xf5, 0xeb, 0x0f, 0x46, 0x36, 0x84, 0x31, 0xbb, 0x82, 0x4b, 0xb8, 0xb5, 0x73, 0x63, 0x7d, 0x13, + 0xdb, 0xbd, 0x69, 0xdb, 0x45, 0xcf, 0x76, 0x8d, 0x74, 0x25, 0xc3, 0x18, 0xc3, 0x1c, 0x67, 0xba, + 0xde, 0x3d, 0x51, 0xe3, 0xe0, 0x27, 0xb6, 0x8d, 0xab, 0xef, 0x23, 0xe5, 0x08, 0x93, 0xd5, 0x4a, + 0xae, 0x67, 0x36, 0x74, 0x05, 0xa9, 0x6e, 0xb3, 0xa9, 0x0d, 0xdd, 0x9a, 0x4d, 0x09, 0xab, 0xd4, + 0x76, 0xb0, 0xa5, 0x52, 0x3e, 0x80, 0xfe, 0x0a, 0x65, 0x56, 0x28, 0x5c, 0xe1, 0x3a, 0xb7, 0x25, + 0x68, 0x9a, 0xf8, 0x5d, 0xe8, 0xe6, 0x72, 0xf1, 0x92, 0xb8, 0x67, 0x3b, 0x99, 0xcb, 0xc5, 0x63, + 0x2c, 0xf9, 0x7b, 0xe0, 0xcd, 0x23, 0x8c, 0x67, 0xda, 0x65, 0xda, 0xd9, 0xd3, 0x86, 0xc7, 0x58, + 0x06, 0x7f, 0x32, 0xe8, 0x4c, 0x50, 0x5d, 0xa1, 0xba, 0x91, 0xa4, 0x34, 0xa5, 0xda, 0x79, 0x8b, + 0x54, 0xb7, 0x0f, 0x4b, 0xb5, 0xbb, 0x95, 0xea, 0x3b, 0xe0, 0x4e, 0x54, 0x78, 0x36, 0xd6, 0x2f, + 0x72, 0x84, 0x01, 0xc4, 0xc6, 0x51, 0x98, 0x47, 0x57, 0x68, 0xf5, 0xdb, 0xa2, 0x3d, 0xa5, 0xe9, + 0x1d, 0x50, 0x9a, 0x3f, 0x18, 0x74, 0x9e, 0xc8, 0x32, 0x29, 0xf2, 0x3d, 0x16, 0x0e, 0xa0, 0x3f, + 0x4a, 0xd3, 0x38, 0x0a, 0xcd, 0x69, 0x93, 0x51, 0xd3, 0x44, 0x3b, 0x9e, 0x36, 0xea, 0x6b, 0x72, + 0x6b, 0x9a, 0x68, 0x8a, 0x4f, 0xb5, 0x9a, 0x1a, 0x69, 0x6c, 0x4c, 0xb1, 0x11, 0x51, 0xed, 0xa4, + 0x22, 0x8c, 0x8a, 0x3c, 0x99, 0xc7, 0xc9, 0xb5, 0xce, 0xb6, 0x27, 0x6a, 0xbc, 0x97, 0x44, 0xe7, + 0x40, 0x12, 0xaf, 0x5b, 0xd0, 0xfe, 0xaf, 0x54, 0xf2, 0x08, 0x58, 0x64, 0x1f, 0xc1, 0xa2, 0x5a, + 0x33, 0xbb, 0x0d, 0xcd, 0xf4, 0xa1, 0x5b, 0x2a, 0xb9, 0x5e, 0x60, 0xe6, 0xf7, 0xb4, 0x02, 0x55, + 0x50, 0x7b, 0xf4, 0xac, 0x19, 0xb1, 0xf4, 0x44, 0x05, 0xeb, 0xd9, 0x81, 0xc6, 0xec, 0x7c, 0x62, + 0x75, 0xb5, 0xaf, 0x5f, 0xe4, 0xef, 0x96, 0xee, 0xdf, 0x93, 0xd3, 0xd7, 0x0c, 0xdc, 0x7a, 0xf0, + 0x4e, 0x77, 0x07, 0xef, 0x74, 0x3b, 0x78, 0xe3, 0x93, 0x6a, 0xf0, 0xc6, 0x27, 0x84, 0xc5, 0x79, + 0x35, 0x78, 0xe2, 0x9c, 0x1a, 0xfa, 0x48, 0x25, 0x45, 0x7a, 0x52, 0x9a, 0xce, 0x7b, 0xa2, 0xc6, + 0xc4, 0xd6, 0xef, 0x96, 0xa8, 0x6c, 0xa9, 0x3d, 0x61, 0x11, 0x71, 0xfb, 0x89, 0x16, 0x25, 0x53, + 0x5c, 0x03, 0xf8, 0x47, 0xe0, 0x0a, 0x2a, 0x9e, 0xae, 0xf0, 0x4e, 0x5f, 0xb4, 0x59, 0x18, 0x2f, + 0x05, 0x35, 0xff, 0x29, 0x4b, 0x72, 0x8b, 0x82, 0x87, 0xf6, 0x38, 0x45, 0xbf, 0x4c, 0x53, 0x54, + 0x76, 0x54, 0x0d, 0xd0, 0x77, 0x26, 0xd7, 0x68, 0x54, 0xd6, 0x11, 0x06, 0x04, 0xdf, 0x83, 0x37, + 0x8a, 0x51, 0xe5, 0xa2, 0x88, 0xf7, 0xb5, 0x99, 0x43, 0xfb, 0x9b, 0xc9, 0xf3, 0x67, 0xd5, 0x80, + 0xd3, 0x7a, 0x3b, 0x96, 0xce, 0x1b, 0x63, 0xf9, 0x58, 0xa6, 0xf2, 0x6c, 0xac, 0x79, 0xe6, 0x08, + 0x8b, 0x82, 0x9f, 0x19, 0xb4, 0x69, 0xfe, 0x1b, 0xa1, 0xdb, 0x6f, 0xd3, 0x8e, 0x73, 0x95, 0x5c, + 0x45, 0x33, 0x54, 0x95, 0x76, 0x54, 0x58, 0x27, 0x1d, 0x2e, 0xb1, 0xfe, 0x00, 0x5a, 0x44, 0xbd, + 0xa6, 0xcf, 0x4f, 0xc5, 0xe5, 0x46, 0xaf, 0xc9, 0x2c, 0x8c, 0x93, 0xbf, 0x0f, 0x30, 0x29, 0x52, + 0x54, 0xa3, 0xd9, 0x2a, 0x32, 0x63, 0xd5, 0x13, 0x0d, 0x4b, 0xf0, 0x95, 0xf9, 0x4e, 0xed, 0x0d, + 0x20, 0x3b, 0xfc, 0xf5, 0x7a, 0xf3, 0xe5, 0xc1, 0xc5, 0xee, 0xb9, 0x1b, 0x65, 0x3b, 0x80, 0xbe, + 0xfd, 0x7b, 0xea, 0x9f, 0x9c, 0x15, 0x94, 0x86, 0x69, 0xda, 0xd1, 0x5f, 0xe9, 0x87, 0x7f, 0x07, + 0x00, 0x00, 0xff, 0xff, 0x90, 0x83, 0x07, 0x1e, 0x5c, 0x0b, 0x00, 0x00, } diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto index 6e0c0f66c..bbfd26c94 100644 --- a/bolt/internal/internal.proto +++ b/bolt/internal/internal.proto @@ -14,6 +14,7 @@ message Source { string MetaURL = 10; // MetaURL is the connection URL for the meta node. string SharedSecret = 11; // SharedSecret signs the optional InfluxDB JWT Authorization string Organization = 12; // Organization is the organization ID that resource belongs to + string Role = 13; // Role is the name of the role that a user must posses to access the resource } message Dashboard { diff --git a/bolt/sources_test.go b/bolt/sources_test.go index cde1af98c..a66e132b3 100644 --- a/bolt/sources_test.go +++ b/bolt/sources_test.go @@ -30,6 +30,7 @@ func TestSourceStore(t *testing.T) { URL: "toyota-hilux.lyon-estates.local", Default: true, Organization: "1337", + Role: "member", }, chronograf.Source{ Name: "HipToBeSquare", @@ -39,6 +40,7 @@ func TestSourceStore(t *testing.T) { URL: "toyota-hilux.lyon-estates.local", Default: true, Organization: "1337", + Role: "admin", }, chronograf.Source{ Name: "HipToBeSquare", @@ -49,6 +51,7 @@ func TestSourceStore(t *testing.T) { InsecureSkipVerify: true, Default: false, Organization: "1337", + Role: "viewer", }, } diff --git a/chronograf.go b/chronograf.go index ddecba3b5..7fe93769a 100644 --- a/chronograf.go +++ b/chronograf.go @@ -450,6 +450,7 @@ type Source struct { Default bool `json:"default"` // Default specifies the default source for the application Telegraf string `json:"telegraf"` // Telegraf is the db telegraf is written to. By default it is "telegraf" Organization string `json:"organization"` // Organization is the organization ID that resource belongs to + Role string `json:"role"` // Role is the name of the role that a user must posses to access the resource. } // SourcesStore stores connection information for a `TimeSeries` From f71e6a4b55dac196efdf71c577f2ae2772932004 Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Fri, 3 Nov 2017 15:32:59 -0400 Subject: [PATCH 02/11] Add roles implementation of sources store Minimal test coverage of Update/Delete/Add methods was done since they do not involve any filtering. The filtering for them should have happened at the API level. --- roles/roles.go | 31 +++ roles/sources.go | 134 ++++++++++++ roles/sources_test.go | 489 ++++++++++++++++++++++++++++++++++++++++++ server/stores.go | 27 ++- server/stores_test.go | 137 ++++++++++++ 5 files changed, 817 insertions(+), 1 deletion(-) create mode 100644 roles/roles.go create mode 100644 roles/sources.go create mode 100644 roles/sources_test.go create mode 100644 server/stores_test.go diff --git a/roles/roles.go b/roles/roles.go new file mode 100644 index 000000000..e5c118c4f --- /dev/null +++ b/roles/roles.go @@ -0,0 +1,31 @@ +package roles + +import ( + "context" + "fmt" +) + +type contextKey string + +// ContextKey is the key used to specify the +// role via context +const ContextKey = contextKey("role") + +func validRole(ctx context.Context) error { + // prevents panic in case of nil context + if ctx == nil { + return fmt.Errorf("expect non nil context") + } + role, ok := ctx.Value(ContextKey).(string) + // should never happen + if !ok { + return fmt.Errorf("expected role key to be a string") + } + switch role { + // TODO(desa): make real roles + case "member", "viewer", "editor", "admin": + return nil + default: + return fmt.Errorf("expected role key to be set") + } +} diff --git a/roles/sources.go b/roles/sources.go new file mode 100644 index 000000000..ae4504623 --- /dev/null +++ b/roles/sources.go @@ -0,0 +1,134 @@ +package roles + +import ( + "context" + + "github.com/influxdata/chronograf" +) + +// ensure that SourcesStore implements chronograf.SourceStore +var _ chronograf.SourcesStore = &SourcesStore{} + +// SourcesStore facade on a SourceStore that filters sources +// by role. +type SourcesStore struct { + store chronograf.SourcesStore + role string +} + +// NewSourcesStore creates a new SourcesStore from an existing +// chronograf.SourceStore and an role string +func NewSourcesStore(s chronograf.SourcesStore, role string) *SourcesStore { + return &SourcesStore{ + store: s, + role: role, + } +} + +// All retrieves all sources from the underlying SourceStore and filters them +// by role. +func (s *SourcesStore) All(ctx context.Context) ([]chronograf.Source, error) { + err := validRole(ctx) + if err != nil { + return nil, err + } + + ds, err := s.store.All(ctx) + if err != nil { + return nil, err + } + + // This filters sources without allocating + // https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating + sources := ds[:0] + for _, d := range ds { + if hasAuthorizedRole(d.Role, s.role) { + sources = append(sources, d) + } + } + + return sources, nil +} + +// Add creates a new Source in the SourcesStore with source.Role set to be the +// role from the source store. +func (s *SourcesStore) Add(ctx context.Context, d chronograf.Source) (chronograf.Source, error) { + err := validRole(ctx) + if err != nil { + return chronograf.Source{}, err + } + + return s.store.Add(ctx, d) +} + +// Delete the source from SourcesStore +func (s *SourcesStore) Delete(ctx context.Context, d chronograf.Source) error { + err := validRole(ctx) + if err != nil { + return err + } + + d, err = s.store.Get(ctx, d.ID) + if err != nil { + return err + } + + return s.store.Delete(ctx, d) +} + +// Get returns a Source if the id exists and belongs to the role that is set. +func (s *SourcesStore) Get(ctx context.Context, id int) (chronograf.Source, error) { + err := validRole(ctx) + if err != nil { + return chronograf.Source{}, err + } + + d, err := s.store.Get(ctx, id) + if err != nil { + return chronograf.Source{}, err + } + + if !hasAuthorizedRole(d.Role, s.role) { + return chronograf.Source{}, chronograf.ErrSourceNotFound + } + + return d, nil +} + +// Update the source in SourcesStore. +func (s *SourcesStore) Update(ctx context.Context, d chronograf.Source) error { + err := validRole(ctx) + if err != nil { + return err + } + + _, err = s.store.Get(ctx, d.ID) + if err != nil { + return err + } + + return s.store.Update(ctx, d) +} + +func hasAuthorizedRole(sourceRole, providedRole string) bool { + // TODO(desa): make real roles + switch sourceRole { + case "viewer": + switch providedRole { + case "viewer", "editor", "admin": + return true + } + case "editor": + switch providedRole { + case "editor", "admin": + return true + } + case "admin": + switch providedRole { + case "admin": + return true + } + } + + return false +} diff --git a/roles/sources_test.go b/roles/sources_test.go new file mode 100644 index 000000000..16371417f --- /dev/null +++ b/roles/sources_test.go @@ -0,0 +1,489 @@ +package roles + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/influxdata/chronograf" + "github.com/influxdata/chronograf/mocks" +) + +func TestSources_Get(t *testing.T) { + type fields struct { + SourcesStore chronograf.SourcesStore + } + type args struct { + role string + id int + } + type wants struct { + source chronograf.Source + err bool + } + + tests := []struct { + name string + fields fields + args args + wants wants + }{ + { + name: "Get viewer source as viewer", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, nil + }, + }, + }, + args: args{ + role: "viewer", + id: 1, + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, + }, + }, + { + name: "Get viewer source as editor", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, nil + }, + }, + }, + args: args{ + role: "editor", + id: 1, + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, + }, + }, + { + name: "Get viewer source as admin", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, nil + }, + }, + }, + args: args{ + role: "admin", + id: 1, + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, + }, + }, + { + name: "Get editor source as editor", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, nil + }, + }, + }, + args: args{ + role: "editor", + id: 1, + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, + }, + }, + { + name: "Get editor source as admin", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, nil + }, + }, + }, + args: args{ + role: "admin", + id: 1, + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, + }, + }, + { + name: "Get editor source as viewer - want error", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, nil + }, + }, + }, + args: args{ + role: "viewer", + id: 1, + }, + wants: wants{ + err: true, + }, + }, + { + name: "Get admin source as admin", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "admin", + }, nil + }, + }, + }, + args: args{ + role: "admin", + id: 1, + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "admin", + }, + }, + }, + { + name: "Get admin source as viewer - want error", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "admin", + }, nil + }, + }, + }, + args: args{ + role: "viewer", + id: 1, + }, + wants: wants{ + err: true, + }, + }, + { + name: "Get admin source as editor - want error", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "admin", + }, nil + }, + }, + }, + args: args{ + role: "editor", + id: 1, + }, + wants: wants{ + err: true, + }, + }, + { + name: "Get source bad context", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "admin", + }, nil + }, + }, + }, + args: args{ + role: "random role", + id: 1, + }, + wants: wants{ + err: true, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + store := NewSourcesStore(tt.fields.SourcesStore, tt.args.role) + + ctx := context.Background() + + if tt.args.role != "" { + ctx = context.WithValue(ctx, ContextKey, tt.args.role) + } + + source, err := store.Get(ctx, tt.args.id) + if (err != nil) != tt.wants.err { + t.Errorf("%q. Store.Sources().Get() error = %v, wantErr %v", tt.name, err, tt.wants.err) + return + } + if diff := cmp.Diff(source, tt.wants.source); diff != "" { + t.Errorf("%q. Store.Sources().Get():\n-got/+want\ndiff %s", tt.name, diff) + } + }) + } +} + +func TestSources_All(t *testing.T) { + type fields struct { + SourcesStore chronograf.SourcesStore + } + type args struct { + role string + } + type wants struct { + sources []chronograf.Source + err bool + } + + tests := []struct { + name string + fields fields + args args + wants wants + }{ + { + name: "Get viewer sources as viewer", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + AllF: func(ctx context.Context) ([]chronograf.Source, error) { + return []chronograf.Source{ + { + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, + { + ID: 2, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, + { + ID: 3, + Name: "my sweet name", + Organization: "0", + Role: "admin", + }, + }, nil + }, + }, + }, + args: args{ + role: "viewer", + }, + wants: wants{ + sources: []chronograf.Source{ + { + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, + }, + }, + }, + { + name: "Get editor sources as editor", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + AllF: func(ctx context.Context) ([]chronograf.Source, error) { + return []chronograf.Source{ + { + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, + { + ID: 2, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, + { + ID: 3, + Name: "my sweet name", + Organization: "0", + Role: "admin", + }, + }, nil + }, + }, + }, + args: args{ + role: "editor", + }, + wants: wants{ + sources: []chronograf.Source{ + { + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, + { + ID: 2, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, + }, + }, + }, + { + name: "Get admin sources as admin", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + AllF: func(ctx context.Context) ([]chronograf.Source, error) { + return []chronograf.Source{ + { + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, + { + ID: 2, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, + { + ID: 3, + Name: "my sweet name", + Organization: "0", + Role: "admin", + }, + }, nil + }, + }, + }, + args: args{ + role: "admin", + }, + wants: wants{ + sources: []chronograf.Source{ + { + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, + { + ID: 2, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, + { + ID: 3, + Name: "my sweet name", + Organization: "0", + Role: "admin", + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + store := NewSourcesStore(tt.fields.SourcesStore, tt.args.role) + + ctx := context.Background() + + if tt.args.role != "" { + ctx = context.WithValue(ctx, ContextKey, tt.args.role) + } + + sources, err := store.All(ctx) + if (err != nil) != tt.wants.err { + t.Errorf("%q. Store.Sources().Get() error = %v, wantErr %v", tt.name, err, tt.wants.err) + return + } + if diff := cmp.Diff(sources, tt.wants.sources); diff != "" { + t.Errorf("%q. Store.Sources().Get():\n-got/+want\ndiff %s", tt.name, diff) + } + }) + } +} diff --git a/server/stores.go b/server/stores.go index caff3d0dc..d9d7131a2 100644 --- a/server/stores.go +++ b/server/stores.go @@ -6,6 +6,7 @@ import ( "github.com/influxdata/chronograf" "github.com/influxdata/chronograf/noop" "github.com/influxdata/chronograf/organizations" + "github.com/influxdata/chronograf/roles" ) // hasOrganizationContext retrieves organization specified on context @@ -26,6 +27,27 @@ func hasOrganizationContext(ctx context.Context) (string, bool) { return orgID, true } +// hasRoleContext retrieves organization specified on context +// under the organizations.ContextKey +func hasRoleContext(ctx context.Context) (string, bool) { + // prevents panic in case of nil context + if ctx == nil { + return "", false + } + role, ok := ctx.Value(roles.ContextKey).(string) + // should never happen + if !ok { + return "", false + } + switch role { + // TODO(desa): make real roles + case "member", "viewer", "editor", "admin": + return role, true + default: + return "", false + } +} + type superAdminKey string // SuperAdminKey is the context key for retrieving is the context @@ -75,7 +97,10 @@ type Store struct { // and a organization.SourcesStore otherwise. func (s *Store) Sources(ctx context.Context) chronograf.SourcesStore { if org, ok := hasOrganizationContext(ctx); ok { - return organizations.NewSourcesStore(s.SourcesStore, org) + store := organizations.NewSourcesStore(s.SourcesStore, org) + if role, ok := hasRoleContext(ctx); ok { + return roles.NewSourcesStore(store, role) + } } return &noop.SourcesStore{} diff --git a/server/stores_test.go b/server/stores_test.go new file mode 100644 index 000000000..728c15280 --- /dev/null +++ b/server/stores_test.go @@ -0,0 +1,137 @@ +package server + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/influxdata/chronograf" + "github.com/influxdata/chronograf/mocks" + "github.com/influxdata/chronograf/organizations" + "github.com/influxdata/chronograf/roles" +) + +func TestStore_SourcesGet(t *testing.T) { + type fields struct { + SourcesStore chronograf.SourcesStore + } + type args struct { + superAdmin bool + organization string + role string + id int + } + type wants struct { + source chronograf.Source + err bool + } + + tests := []struct { + name string + fields fields + args args + wants wants + }{ + { + name: "Get user as super admin", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, nil + }, + }, + }, + args: args{ + superAdmin: true, + organization: "0", + role: "viewer", + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, + }, + }, + { + name: "Get user as super admin", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, nil + }, + }, + }, + args: args{ + superAdmin: true, + }, + wants: wants{ + err: true, + }, + }, + { + name: "Get user as super admin", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, nil + }, + }, + }, + args: args{ + superAdmin: true, + }, + wants: wants{ + err: true, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + store := &Store{ + SourcesStore: tt.fields.SourcesStore, + } + + ctx := context.Background() + + if tt.args.superAdmin { + ctx = context.WithValue(ctx, SuperAdminKey, true) + } + + if tt.args.organization != "" { + ctx = context.WithValue(ctx, organizations.ContextKey, tt.args.organization) + } + + if tt.args.role != "" { + ctx = context.WithValue(ctx, roles.ContextKey, tt.args.role) + } + + source, err := store.Sources(ctx).Get(ctx, tt.args.id) + if (err != nil) != tt.wants.err { + t.Errorf("%q. Store.Sources().Get() error = %v, wantErr %v", tt.name, err, tt.wants.err) + return + } + if diff := cmp.Diff(source, tt.wants.source); diff != "" { + t.Errorf("%q. Store.Sources().Get():\n-got/+want\ndiff %s", tt.name, diff) + } + }) + } +} From 5e1ad82660d65afe28e15a49eef55149372099fe Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Fri, 3 Nov 2017 16:06:20 -0400 Subject: [PATCH 03/11] Add role ctx where appropriate in AuthorizedUser --- server/auth.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/server/auth.go b/server/auth.go index 5516064f4..ccdb40219 100644 --- a/server/auth.go +++ b/server/auth.go @@ -8,6 +8,7 @@ import ( "github.com/influxdata/chronograf" "github.com/influxdata/chronograf/oauth2" "github.com/influxdata/chronograf/organizations" + "github.com/influxdata/chronograf/roles" ) // AuthorizedToken extracts the token and validates; if valid the next handler @@ -68,6 +69,8 @@ func AuthorizedUser( return } ctx = context.WithValue(ctx, organizations.ContextKey, fmt.Sprintf("%d", defaultOrg.ID)) + // TODO(desa): remove this in place of actual string value + ctx = context.WithValue(ctx, roles.ContextKey, "admin") r = r.WithContext(ctx) next(w, r) return @@ -120,6 +123,9 @@ func AuthorizedUser( ctx = context.WithValue(ctx, organizations.ContextKey, p.Organization) serverCtx := context.WithValue(ctx, SuperAdminKey, true) + // the DataStore expects that the roles context key be set for future calls + // TODO(desa): remove hard coding + serverCtx = context.WithValue(serverCtx, roles.ContextKey, "admin") // TODO: seems silly to look up a user twice u, err := store.Users(serverCtx).Get(serverCtx, chronograf.UserQuery{ Name: &p.Subject, @@ -152,6 +158,10 @@ func AuthorizedUser( } if hasAuthorizedRole(u, role) { + // use the first role, since there should only ever be one + // for any particular organization and hasAuthorizedRole + // should ensure that at least one role for the org exists + ctx = context.WithValue(ctx, roles.ContextKey, u.Roles[0].Name) r = r.WithContext(ctx) next(w, r) return From 14459c7f7bc97b5de69029f2431fc159ed18cc88 Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Fri, 3 Nov 2017 16:11:28 -0400 Subject: [PATCH 04/11] Allow update of sources.Role in server.UpdateSource --- server/sources.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/sources.go b/server/sources.go index 4c145f572..0586ee658 100644 --- a/server/sources.go +++ b/server/sources.go @@ -259,6 +259,9 @@ func (s *Service) UpdateSource(w http.ResponseWriter, r *http.Request) { if req.Telegraf != "" { src.Telegraf = req.Telegraf } + if req.Role != "" { + src.Role = req.Role + } defaultOrg, err := s.Store.Organizations(ctx).DefaultOrganization(ctx) if err != nil { @@ -310,6 +313,11 @@ func ValidSourceRequest(s chronograf.Source, defaultOrgID string) error { if len(url.Scheme) == 0 { return fmt.Errorf("Invalid URL; no URL scheme defined") } + + if s.Role == "" { + // TODO(desa): removed bare string here + s.Role = "viewer" + } return nil } From 859d94ab15f1261466b84a5383b97d6dd4b2b7d2 Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Fri, 3 Nov 2017 16:32:05 -0400 Subject: [PATCH 05/11] Move user roles and role names to roles package --- roles/roles.go | 36 ++++++++++++++++++++++++++++++++++-- roles/sources.go | 13 ++++++------- server/auth.go | 19 +++++++++---------- server/auth_test.go | 43 ++++++++++++++++++++++--------------------- server/me.go | 3 ++- server/me_test.go | 7 ++++--- server/mux.go | 9 +++++---- server/sources.go | 4 ++-- server/stores.go | 3 +-- server/users.go | 36 +++--------------------------------- server/users_test.go | 23 ++++++++++++----------- 11 files changed, 100 insertions(+), 96 deletions(-) diff --git a/roles/roles.go b/roles/roles.go index e5c118c4f..b5019a9ca 100644 --- a/roles/roles.go +++ b/roles/roles.go @@ -3,6 +3,8 @@ package roles import ( "context" "fmt" + + "github.com/influxdata/chronograf" ) type contextKey string @@ -22,10 +24,40 @@ func validRole(ctx context.Context) error { return fmt.Errorf("expected role key to be a string") } switch role { - // TODO(desa): make real roles - case "member", "viewer", "editor", "admin": + case MemberRoleName, ViewerRoleName, EditorRoleName, AdminRoleName: return nil default: return fmt.Errorf("expected role key to be set") } } + +// Chronograf User Roles +const ( + MemberRoleName = "member" + ViewerRoleName = "viewer" + EditorRoleName = "editor" + AdminRoleName = "admin" + SuperAdminRoleName = "superadmin" +) + +var ( + // MemberRole is the role for a user who can only perform No operations. + MemberRole = chronograf.Role{ + Name: MemberRoleName, + } + + // ViewerRole is the role for a user who can only perform READ operations on Dashboards, Rules, and Sources + ViewerRole = chronograf.Role{ + Name: ViewerRoleName, + } + + // EditorRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, and Sources + EditorRole = chronograf.Role{ + Name: EditorRoleName, + } + + // AdminRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, Sources, and Users + AdminRole = chronograf.Role{ + Name: AdminRoleName, + } +) diff --git a/roles/sources.go b/roles/sources.go index ae4504623..e2b65145d 100644 --- a/roles/sources.go +++ b/roles/sources.go @@ -111,21 +111,20 @@ func (s *SourcesStore) Update(ctx context.Context, d chronograf.Source) error { } func hasAuthorizedRole(sourceRole, providedRole string) bool { - // TODO(desa): make real roles switch sourceRole { - case "viewer": + case ViewerRoleName: switch providedRole { - case "viewer", "editor", "admin": + case ViewerRoleName, EditorRoleName, AdminRoleName: return true } - case "editor": + case EditorRoleName: switch providedRole { - case "editor", "admin": + case EditorRoleName, AdminRoleName: return true } - case "admin": + case AdminRoleName: switch providedRole { - case "admin": + case AdminRoleName: return true } } diff --git a/server/auth.go b/server/auth.go index ccdb40219..7cb28342e 100644 --- a/server/auth.go +++ b/server/auth.go @@ -70,7 +70,7 @@ func AuthorizedUser( } ctx = context.WithValue(ctx, organizations.ContextKey, fmt.Sprintf("%d", defaultOrg.ID)) // TODO(desa): remove this in place of actual string value - ctx = context.WithValue(ctx, roles.ContextKey, "admin") + ctx = context.WithValue(ctx, roles.ContextKey, roles.AdminRoleName) r = r.WithContext(ctx) next(w, r) return @@ -124,8 +124,7 @@ func AuthorizedUser( ctx = context.WithValue(ctx, organizations.ContextKey, p.Organization) serverCtx := context.WithValue(ctx, SuperAdminKey, true) // the DataStore expects that the roles context key be set for future calls - // TODO(desa): remove hard coding - serverCtx = context.WithValue(serverCtx, roles.ContextKey, "admin") + serverCtx = context.WithValue(serverCtx, roles.ContextKey, roles.AdminRoleName) // TODO: seems silly to look up a user twice u, err := store.Users(serverCtx).Get(serverCtx, chronograf.UserQuery{ Name: &p.Subject, @@ -178,28 +177,28 @@ func hasAuthorizedRole(u *chronograf.User, role string) bool { } switch role { - case ViewerRoleName: + case roles.ViewerRoleName: for _, r := range u.Roles { switch r.Name { - case ViewerRoleName, EditorRoleName, AdminRoleName: + case roles.ViewerRoleName, roles.EditorRoleName, roles.AdminRoleName: return true } } - case EditorRoleName: + case roles.EditorRoleName: for _, r := range u.Roles { switch r.Name { - case EditorRoleName, AdminRoleName: + case roles.EditorRoleName, roles.AdminRoleName: return true } } - case AdminRoleName: + case roles.AdminRoleName: for _, r := range u.Roles { switch r.Name { - case AdminRoleName: + case roles.AdminRoleName: return true } } - case SuperAdminRoleName: + case roles.SuperAdminRoleName: // SuperAdmins should have been authorized before this. // This is only meant to restrict access for non-superadmins. return false diff --git a/server/auth_test.go b/server/auth_test.go index 1ef3b8c84..c7b8f7ed4 100644 --- a/server/auth_test.go +++ b/server/auth_test.go @@ -12,6 +12,7 @@ import ( clog "github.com/influxdata/chronograf/log" "github.com/influxdata/chronograf/mocks" "github.com/influxdata/chronograf/oauth2" + "github.com/influxdata/chronograf/roles" "github.com/influxdata/chronograf/server" ) @@ -115,7 +116,7 @@ func TestAuthorizedUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: server.ViewerRoleName, + Name: roles.ViewerRoleName, Organization: "1337", }, }, @@ -167,7 +168,7 @@ func TestAuthorizedUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: server.EditorRoleName, + Name: roles.EditorRoleName, Organization: "1337", }, }, @@ -219,7 +220,7 @@ func TestAuthorizedUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: server.AdminRoleName, + Name: roles.AdminRoleName, Organization: "1337", }, }, @@ -271,7 +272,7 @@ func TestAuthorizedUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: server.ViewerRoleName, + Name: roles.ViewerRoleName, Organization: "1337", }, }, @@ -323,7 +324,7 @@ func TestAuthorizedUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: server.EditorRoleName, + Name: roles.EditorRoleName, Organization: "1337", }, }, @@ -375,7 +376,7 @@ func TestAuthorizedUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: server.AdminRoleName, + Name: roles.AdminRoleName, Organization: "1337", }, }, @@ -427,7 +428,7 @@ func TestAuthorizedUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: server.ViewerRoleName, + Name: roles.ViewerRoleName, Organization: "1337", }, }, @@ -479,7 +480,7 @@ func TestAuthorizedUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: server.EditorRoleName, + Name: roles.EditorRoleName, Organization: "1337", }, }, @@ -531,7 +532,7 @@ func TestAuthorizedUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: server.AdminRoleName, + Name: roles.AdminRoleName, Organization: "1337", }, }, @@ -872,7 +873,7 @@ func TestAuthorizedUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: server.ViewerRoleName, + Name: roles.ViewerRoleName, Organization: "1337", }, }, @@ -924,7 +925,7 @@ func TestAuthorizedUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: server.EditorRoleName, + Name: roles.EditorRoleName, Organization: "1337", }, }, @@ -976,7 +977,7 @@ func TestAuthorizedUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: server.AdminRoleName, + Name: roles.AdminRoleName, Organization: "1337", }, }, @@ -1029,7 +1030,7 @@ func TestAuthorizedUser(t *testing.T) { SuperAdmin: true, Roles: []chronograf.Role{ { - Name: server.MemberRoleName, + Name: roles.MemberRoleName, Organization: "1337", }, }, @@ -1082,7 +1083,7 @@ func TestAuthorizedUser(t *testing.T) { SuperAdmin: true, Roles: []chronograf.Role{ { - Name: server.MemberRoleName, + Name: roles.MemberRoleName, Organization: "1337", }, }, @@ -1135,7 +1136,7 @@ func TestAuthorizedUser(t *testing.T) { SuperAdmin: true, Roles: []chronograf.Role{ { - Name: server.MemberRoleName, + Name: roles.MemberRoleName, Organization: "1337", }, }, @@ -1188,7 +1189,7 @@ func TestAuthorizedUser(t *testing.T) { SuperAdmin: true, Roles: []chronograf.Role{ { - Name: server.MemberRoleName, + Name: roles.MemberRoleName, Organization: "1337", }, }, @@ -1240,7 +1241,7 @@ func TestAuthorizedUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: server.AdminRoleName, + Name: roles.AdminRoleName, Organization: "1337", }, }, @@ -1288,7 +1289,7 @@ func TestAuthorizedUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: server.AdminRoleName, + Name: roles.AdminRoleName, Organization: "1337", }, }, @@ -1339,7 +1340,7 @@ func TestAuthorizedUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: server.AdminRoleName, + Name: roles.AdminRoleName, Organization: "1337", }, }, @@ -1391,7 +1392,7 @@ func TestAuthorizedUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: server.AdminRoleName, + Name: roles.AdminRoleName, Organization: "1337", }, }, @@ -1450,7 +1451,7 @@ func TestAuthorizedUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: server.AdminRoleName, + Name: roles.AdminRoleName, Organization: "1337", }, }, diff --git a/server/me.go b/server/me.go index 72a0b5708..2cb49fc18 100644 --- a/server/me.go +++ b/server/me.go @@ -10,6 +10,7 @@ import ( "github.com/influxdata/chronograf" "github.com/influxdata/chronograf/oauth2" "github.com/influxdata/chronograf/organizations" + "github.com/influxdata/chronograf/roles" ) type meLinks struct { @@ -249,7 +250,7 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { Scheme: scheme, Roles: []chronograf.Role{ { - Name: MemberRoleName, + Name: roles.MemberRoleName, // This is the ID of the default organization Organization: fmt.Sprintf("%d", defaultOrg.ID), }, diff --git a/server/me_test.go b/server/me_test.go index 1a9d4236f..159cfb0cf 100644 --- a/server/me_test.go +++ b/server/me_test.go @@ -14,6 +14,7 @@ import ( "github.com/influxdata/chronograf/log" "github.com/influxdata/chronograf/mocks" "github.com/influxdata/chronograf/oauth2" + "github.com/influxdata/chronograf/roles" ) type MockUsers struct{} @@ -295,7 +296,7 @@ func TestService_MeOrganizations(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: AdminRoleName, + Name: roles.AdminRoleName, Organization: "1337", }, }, @@ -354,7 +355,7 @@ func TestService_MeOrganizations(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: AdminRoleName, + Name: roles.AdminRoleName, Organization: "1337", }, }, @@ -465,7 +466,7 @@ func TestService_MeOrganizations(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: AdminRoleName, + Name: roles.AdminRoleName, Organization: "1337", }, }, diff --git a/server/mux.go b/server/mux.go index 84f3cb365..7e7c2c273 100644 --- a/server/mux.go +++ b/server/mux.go @@ -12,6 +12,7 @@ import ( "github.com/bouk/httprouter" "github.com/influxdata/chronograf" // When julienschmidt/httprouter v2 w/ context is out, switch "github.com/influxdata/chronograf/oauth2" + "github.com/influxdata/chronograf/roles" ) const ( @@ -71,7 +72,7 @@ func NewMux(opts MuxOpts, service Service) http.Handler { return AuthorizedUser( service.Store, opts.UseAuth, - ViewerRoleName, + roles.ViewerRoleName, opts.Logger, next, ) @@ -80,7 +81,7 @@ func NewMux(opts MuxOpts, service Service) http.Handler { return AuthorizedUser( service.Store, opts.UseAuth, - EditorRoleName, + roles.EditorRoleName, opts.Logger, next, ) @@ -89,7 +90,7 @@ func NewMux(opts MuxOpts, service Service) http.Handler { return AuthorizedUser( service.Store, opts.UseAuth, - AdminRoleName, + roles.AdminRoleName, opts.Logger, next, ) @@ -98,7 +99,7 @@ func NewMux(opts MuxOpts, service Service) http.Handler { return AuthorizedUser( service.Store, opts.UseAuth, - SuperAdminRoleName, + roles.SuperAdminRoleName, opts.Logger, next, ) diff --git a/server/sources.go b/server/sources.go index 0586ee658..b87342c4a 100644 --- a/server/sources.go +++ b/server/sources.go @@ -10,6 +10,7 @@ import ( "github.com/bouk/httprouter" "github.com/influxdata/chronograf" "github.com/influxdata/chronograf/influx" + "github.com/influxdata/chronograf/roles" ) type sourceLinks struct { @@ -315,8 +316,7 @@ func ValidSourceRequest(s chronograf.Source, defaultOrgID string) error { } if s.Role == "" { - // TODO(desa): removed bare string here - s.Role = "viewer" + s.Role = roles.ViewerRoleName } return nil } diff --git a/server/stores.go b/server/stores.go index d9d7131a2..630c124cb 100644 --- a/server/stores.go +++ b/server/stores.go @@ -40,8 +40,7 @@ func hasRoleContext(ctx context.Context) (string, bool) { return "", false } switch role { - // TODO(desa): make real roles - case "member", "viewer", "editor", "admin": + case roles.MemberRoleName, roles.ViewerRoleName, roles.EditorRoleName, roles.AdminRoleName: return role, true default: return "", false diff --git a/server/users.go b/server/users.go index 73c1af869..10a3d99ef 100644 --- a/server/users.go +++ b/server/users.go @@ -10,6 +10,7 @@ import ( "github.com/bouk/httprouter" "github.com/influxdata/chronograf" + "github.com/influxdata/chronograf/roles" ) type userRequest struct { @@ -64,10 +65,10 @@ func (r *userRequest) ValidRoles() error { } orgs[r.Organization] = true switch r.Name { - case MemberRoleName, ViewerRoleName, EditorRoleName, AdminRoleName: + case roles.MemberRoleName, roles.ViewerRoleName, roles.EditorRoleName, roles.AdminRoleName: continue default: - return fmt.Errorf("Unknown role %s. Valid roles are 'viewer', 'editor', 'admin', and 'superadmin'", r.Name) + return fmt.Errorf("Unknown role %s. Valid roles are 'member', 'viewer', 'editor', 'admin', and 'superadmin'", r.Name) } } } @@ -125,37 +126,6 @@ func newUsersResponse(users []chronograf.User) *usersResponse { } } -// Chronograf User Roles -const ( - MemberRoleName = "member" - ViewerRoleName = "viewer" - EditorRoleName = "editor" - AdminRoleName = "admin" - SuperAdminRoleName = "superadmin" -) - -var ( - // MemberRole is the role for a user who can only perform No operations. - MemberRole = chronograf.Role{ - Name: MemberRoleName, - } - - // ViewerRole is the role for a user who can only perform READ operations on Dashboards, Rules, and Sources - ViewerRole = chronograf.Role{ - Name: ViewerRoleName, - } - - // EditorRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, and Sources - EditorRole = chronograf.Role{ - Name: EditorRoleName, - } - - // AdminRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, Sources, and Users - AdminRole = chronograf.Role{ - Name: AdminRoleName, - } -) - // UserID retrieves a Chronograf user with ID from store func (s *Service) UserID(w http.ResponseWriter, r *http.Request) { ctx := r.Context() diff --git a/server/users_test.go b/server/users_test.go index d1121e380..48669be22 100644 --- a/server/users_test.go +++ b/server/users_test.go @@ -14,6 +14,7 @@ import ( "github.com/influxdata/chronograf" "github.com/influxdata/chronograf/log" "github.com/influxdata/chronograf/mocks" + "github.com/influxdata/chronograf/roles" ) func TestService_UserID(t *testing.T) { @@ -56,7 +57,7 @@ func TestService_UserID(t *testing.T) { Provider: "google", Scheme: "oauth2", Roles: []chronograf.Role{ - ViewerRole, + roles.ViewerRole, }, }, nil default: @@ -501,7 +502,7 @@ func TestService_UpdateUser(t *testing.T) { Provider: "github", Scheme: "oauth2", Roles: []chronograf.Role{ - EditorRole, + roles.EditorRole, }, }, nil default: @@ -520,7 +521,7 @@ func TestService_UpdateUser(t *testing.T) { user: &userRequest{ ID: 1336, Roles: []chronograf.Role{ - AdminRole, + roles.AdminRole, }, }, }, @@ -803,7 +804,7 @@ func TestService_Users(t *testing.T) { Provider: "google", Scheme: "oauth2", Roles: []chronograf.Role{ - EditorRole, + roles.EditorRole, }, }, { @@ -847,7 +848,7 @@ func TestService_Users(t *testing.T) { Provider: "google", Scheme: "oauth2", Roles: []chronograf.Role{ - EditorRole, + roles.EditorRole, }, }, }, nil @@ -915,7 +916,7 @@ func TestUserRequest_ValidCreate(t *testing.T) { Provider: "auth0", Scheme: "oauth2", Roles: []chronograf.Role{ - EditorRole, + roles.EditorRole, }, }, }, @@ -930,7 +931,7 @@ func TestUserRequest_ValidCreate(t *testing.T) { Provider: "auth0", Scheme: "oauth2", Roles: []chronograf.Role{ - EditorRole, + roles.EditorRole, }, }, }, @@ -945,7 +946,7 @@ func TestUserRequest_ValidCreate(t *testing.T) { Name: "billietta", Scheme: "oauth2", Roles: []chronograf.Role{ - EditorRole, + roles.EditorRole, }, }, }, @@ -960,7 +961,7 @@ func TestUserRequest_ValidCreate(t *testing.T) { Name: "billietta", Provider: "auth0", Roles: []chronograf.Role{ - EditorRole, + roles.EditorRole, }, }, }, @@ -983,7 +984,7 @@ func TestUserRequest_ValidCreate(t *testing.T) { }, }, wantErr: true, - err: fmt.Errorf("Unknown role BilliettaSpecialRole. Valid roles are 'viewer', 'editor', 'admin', and 'superadmin'"), + err: fmt.Errorf("Unknown role BilliettaSpecialRole. Valid roles are 'member', 'viewer', 'editor', 'admin', and 'superadmin'"), }, } @@ -1020,7 +1021,7 @@ func TestUserRequest_ValidUpdate(t *testing.T) { u: &userRequest{ ID: 1337, Roles: []chronograf.Role{ - EditorRole, + roles.EditorRole, }, }, }, From 9a548983d0b0e57ba865ebf5b4504b28adefd92d Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Mon, 6 Nov 2017 11:31:44 -0500 Subject: [PATCH 06/11] Fix role related tests after rebase --- server/me.go | 2 +- server/organizations.go | 5 +++-- server/users_test.go | 36 ++++++++++++++++++------------------ 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/server/me.go b/server/me.go index 2cb49fc18..41f627c30 100644 --- a/server/me.go +++ b/server/me.go @@ -220,7 +220,7 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { if !hasRoleInDefaultOrganization(usr) { usr.Roles = append(usr.Roles, chronograf.Role{ Organization: "0", - Name: MemberRoleName, + Name: roles.MemberRoleName, }) if err := s.Store.Users(ctx).Update(ctx, usr); err != nil { unknownErrorWithMessage(w, err, s.Logger) diff --git a/server/organizations.go b/server/organizations.go index a61274c8d..abac14776 100644 --- a/server/organizations.go +++ b/server/organizations.go @@ -8,6 +8,7 @@ import ( "github.com/bouk/httprouter" "github.com/influxdata/chronograf" + "github.com/influxdata/chronograf/roles" ) func parseOrganizationID(id string) (uint64, error) { @@ -36,11 +37,11 @@ func (r *organizationRequest) ValidUpdate() error { func (r *organizationRequest) ValidDefaultRole() error { if r.DefaultRole == "" { - r.DefaultRole = MemberRoleName + r.DefaultRole = roles.MemberRoleName } switch r.DefaultRole { - case MemberRoleName, ViewerRoleName, EditorRoleName, AdminRoleName: + case roles.MemberRoleName, roles.ViewerRoleName, roles.EditorRoleName, roles.AdminRoleName: return nil default: return fmt.Errorf("default role must be member, viewer, editor, or admin") diff --git a/server/users_test.go b/server/users_test.go index 48669be22..e1e221b32 100644 --- a/server/users_test.go +++ b/server/users_test.go @@ -177,11 +177,11 @@ func TestService_NewUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: AdminRoleName, + Name: roles.AdminRoleName, Organization: "bobbetta org", }, { - Name: ViewerRoleName, + Name: roles.ViewerRoleName, Organization: "billieta org", }, }, @@ -198,11 +198,11 @@ func TestService_NewUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: AdminRoleName, + Name: roles.AdminRoleName, Organization: "bobbetta org", }, { - Name: ViewerRoleName, + Name: roles.ViewerRoleName, Organization: "billieta org", }, }, @@ -229,11 +229,11 @@ func TestService_NewUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: AdminRoleName, + Name: roles.AdminRoleName, Organization: "bobbetta org", }, { - Name: ViewerRoleName, + Name: roles.ViewerRoleName, Organization: "bobbetta org", }, }, @@ -250,11 +250,11 @@ func TestService_NewUser(t *testing.T) { Scheme: "oauth2", Roles: []chronograf.Role{ { - Name: AdminRoleName, + Name: roles.AdminRoleName, Organization: "bobbetta org", }, { - Name: ViewerRoleName, + Name: roles.ViewerRoleName, Organization: "bobbetta org", }, }, @@ -547,7 +547,7 @@ func TestService_UpdateUser(t *testing.T) { Provider: "github", Scheme: "oauth2", Roles: []chronograf.Role{ - EditorRole, + roles.EditorRole, }, }, nil default: @@ -567,11 +567,11 @@ func TestService_UpdateUser(t *testing.T) { ID: 1336, Roles: []chronograf.Role{ { - Name: AdminRoleName, + Name: roles.AdminRoleName, Organization: "bobbetta org", }, { - Name: ViewerRoleName, + Name: roles.ViewerRoleName, Organization: "billieta org", }, }, @@ -599,7 +599,7 @@ func TestService_UpdateUser(t *testing.T) { Provider: "github", Scheme: "oauth2", Roles: []chronograf.Role{ - EditorRole, + roles.EditorRole, }, }, nil default: @@ -619,11 +619,11 @@ func TestService_UpdateUser(t *testing.T) { ID: 1336, Roles: []chronograf.Role{ { - Name: AdminRoleName, + Name: roles.AdminRoleName, Organization: "bobbetta org", }, { - Name: ViewerRoleName, + Name: roles.ViewerRoleName, Organization: "bobbetta org", }, }, @@ -651,7 +651,7 @@ func TestService_UpdateUser(t *testing.T) { Provider: "github", Scheme: "oauth2", Roles: []chronograf.Role{ - EditorRole, + roles.EditorRole, }, }, nil default: @@ -672,7 +672,7 @@ func TestService_UpdateUser(t *testing.T) { ID: 1336, SuperAdmin: true, Roles: []chronograf.Role{ - AdminRole, + roles.AdminRole, }, }, }, @@ -698,7 +698,7 @@ func TestService_UpdateUser(t *testing.T) { Provider: "github", Scheme: "oauth2", Roles: []chronograf.Role{ - EditorRole, + roles.EditorRole, }, }, nil default: @@ -719,7 +719,7 @@ func TestService_UpdateUser(t *testing.T) { ID: 1336, SuperAdmin: true, Roles: []chronograf.Role{ - AdminRole, + roles.AdminRole, }, }, }, From cbbf9d96d7e1daed11ac0221a98b862fefc7d46d Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Tue, 7 Nov 2017 13:59:40 -0500 Subject: [PATCH 07/11] Add comments suggested from PR review Add tests to server/stores_test.go --- bolt/internal/internal.proto | 2 +- chronograf.go | 2 +- roles/roles.go | 6 +- roles/sources.go | 7 +- server/auth.go | 1 - server/stores_test.go | 259 ++++++++++++++++++++++++++++++++++- server/users.go | 2 +- server/users_test.go | 2 +- 8 files changed, 268 insertions(+), 13 deletions(-) diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto index bbfd26c94..428d8f296 100644 --- a/bolt/internal/internal.proto +++ b/bolt/internal/internal.proto @@ -14,7 +14,7 @@ message Source { string MetaURL = 10; // MetaURL is the connection URL for the meta node. string SharedSecret = 11; // SharedSecret signs the optional InfluxDB JWT Authorization string Organization = 12; // Organization is the organization ID that resource belongs to - string Role = 13; // Role is the name of the role that a user must posses to access the resource + string Role = 13; // Role is the name of the miniumum role that a user must possess to access the resource } message Dashboard { diff --git a/chronograf.go b/chronograf.go index 7fe93769a..b540b1d2d 100644 --- a/chronograf.go +++ b/chronograf.go @@ -450,7 +450,7 @@ type Source struct { Default bool `json:"default"` // Default specifies the default source for the application Telegraf string `json:"telegraf"` // Telegraf is the db telegraf is written to. By default it is "telegraf" Organization string `json:"organization"` // Organization is the organization ID that resource belongs to - Role string `json:"role"` // Role is the name of the role that a user must posses to access the resource. + Role string `json:"role"` // Role is the name of the minimum role that a user must possess to access the resource. } // SourcesStore stores connection information for a `TimeSeries` diff --git a/roles/roles.go b/roles/roles.go index b5019a9ca..e0ecbd2c1 100644 --- a/roles/roles.go +++ b/roles/roles.go @@ -46,17 +46,17 @@ var ( Name: MemberRoleName, } - // ViewerRole is the role for a user who can only perform READ operations on Dashboards, Rules, and Sources + // ViewerRole is the role for a user who can only perform READ operations on Dashboards, Rules, Sources, and Servers, ViewerRole = chronograf.Role{ Name: ViewerRoleName, } - // EditorRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, and Sources + // EditorRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, Sources, and Servers. EditorRole = chronograf.Role{ Name: EditorRoleName, } - // AdminRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, Sources, and Users + // AdminRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, Sources, Servers, and Users AdminRole = chronograf.Role{ Name: AdminRoleName, } diff --git a/roles/sources.go b/roles/sources.go index e2b65145d..cc69eddb3 100644 --- a/roles/sources.go +++ b/roles/sources.go @@ -10,7 +10,10 @@ import ( var _ chronograf.SourcesStore = &SourcesStore{} // SourcesStore facade on a SourceStore that filters sources -// by role. +// by minimum role required to access the source. +// +// The role is passed around on the context and set when the +// SourcesStore is instantiated. type SourcesStore struct { store chronograf.SourcesStore role string @@ -110,6 +113,8 @@ func (s *SourcesStore) Update(ctx context.Context, d chronograf.Source) error { return s.store.Update(ctx, d) } +// hasAuthorizedRole checks that the role provided has at least +// the minimum role required. func hasAuthorizedRole(sourceRole, providedRole string) bool { switch sourceRole { case ViewerRoleName: diff --git a/server/auth.go b/server/auth.go index 7cb28342e..79a0677e8 100644 --- a/server/auth.go +++ b/server/auth.go @@ -69,7 +69,6 @@ func AuthorizedUser( return } ctx = context.WithValue(ctx, organizations.ContextKey, fmt.Sprintf("%d", defaultOrg.ID)) - // TODO(desa): remove this in place of actual string value ctx = context.WithValue(ctx, roles.ContextKey, roles.AdminRoleName) r = r.WithContext(ctx) next(w, r) diff --git a/server/stores_test.go b/server/stores_test.go index 728c15280..50f162517 100644 --- a/server/stores_test.go +++ b/server/stores_test.go @@ -33,7 +33,7 @@ func TestStore_SourcesGet(t *testing.T) { wants wants }{ { - name: "Get user as super admin", + name: "Get viewer source as viewer", fields: fields{ SourcesStore: &mocks.SourcesStore{ GetF: func(ctx context.Context, id int) (chronograf.Source, error) { @@ -47,7 +47,6 @@ func TestStore_SourcesGet(t *testing.T) { }, }, args: args{ - superAdmin: true, organization: "0", role: "viewer", }, @@ -61,7 +60,236 @@ func TestStore_SourcesGet(t *testing.T) { }, }, { - name: "Get user as super admin", + name: "Get viewer source as editor", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "editor", + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, + }, + }, + { + name: "Get viewer source as admin", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "admin", + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, + }, + }, + { + name: "Get admin source as viewer", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "admin", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "viewer", + }, + wants: wants{ + err: true, + }, + }, + { + name: "Get editor source as viewer", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "viewer", + }, + wants: wants{ + err: true, + }, + }, + { + name: "Get editor source as editor", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "editor", + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, + }, + }, + { + name: "Get editor source as admin", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "admin", + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, + }, + }, + { + name: "Get editor source as viewer", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "editor", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "viewer", + }, + wants: wants{ + err: true, + }, + }, + { + name: "Get admin source as admin", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "admin", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "admin", + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "admin", + }, + }, + }, + { + name: "Get source as super admin", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, nil + }, + }, + }, + args: args{ + organization: "0", + role: "viewer", + superAdmin: true, + }, + wants: wants{ + source: chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, + }, + }, + { + name: "Get source as super admin - no organization or role", fields: fields{ SourcesStore: &mocks.SourcesStore{ GetF: func(ctx context.Context, id int) (chronograf.Source, error) { @@ -82,7 +310,29 @@ func TestStore_SourcesGet(t *testing.T) { }, }, { - name: "Get user as super admin", + name: "Get source as super admin - no role", + fields: fields{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, id int) (chronograf.Source, error) { + return chronograf.Source{ + ID: 1, + Name: "my sweet name", + Organization: "0", + Role: "viewer", + }, nil + }, + }, + }, + args: args{ + superAdmin: true, + organization: "0", + }, + wants: wants{ + err: true, + }, + }, + { + name: "Get source as super admin - no organization", fields: fields{ SourcesStore: &mocks.SourcesStore{ GetF: func(ctx context.Context, id int) (chronograf.Source, error) { @@ -97,6 +347,7 @@ func TestStore_SourcesGet(t *testing.T) { }, args: args{ superAdmin: true, + role: "viewer", }, wants: wants{ err: true, diff --git a/server/users.go b/server/users.go index 10a3d99ef..c6cee439d 100644 --- a/server/users.go +++ b/server/users.go @@ -68,7 +68,7 @@ func (r *userRequest) ValidRoles() error { case roles.MemberRoleName, roles.ViewerRoleName, roles.EditorRoleName, roles.AdminRoleName: continue default: - return fmt.Errorf("Unknown role %s. Valid roles are 'member', 'viewer', 'editor', 'admin', and 'superadmin'", r.Name) + return fmt.Errorf("Unknown role %s. Valid roles are 'member', 'viewer', 'editor', and 'admin'", r.Name) } } } diff --git a/server/users_test.go b/server/users_test.go index e1e221b32..ff8cb6e3b 100644 --- a/server/users_test.go +++ b/server/users_test.go @@ -984,7 +984,7 @@ func TestUserRequest_ValidCreate(t *testing.T) { }, }, wantErr: true, - err: fmt.Errorf("Unknown role BilliettaSpecialRole. Valid roles are 'member', 'viewer', 'editor', 'admin', and 'superadmin'"), + err: fmt.Errorf("Unknown role BilliettaSpecialRole. Valid roles are 'member', 'viewer', 'editor', and 'admin'"), }, } From 6f66dd8a476bc5e880e219d93f373cbd5a2d7e9a Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Tue, 7 Nov 2017 13:59:51 -0500 Subject: [PATCH 08/11] Change SuperAdminRoleName to SuperAdminStatus SuperAdmin is not a role type, but it a status of a user that is tangentially related to a users role in an organization. This renames the variable to reflect that difference. --- roles/roles.go | 10 +++++----- server/auth.go | 2 +- server/mux.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/roles/roles.go b/roles/roles.go index e0ecbd2c1..53b9b9632 100644 --- a/roles/roles.go +++ b/roles/roles.go @@ -33,11 +33,11 @@ func validRole(ctx context.Context) error { // Chronograf User Roles const ( - MemberRoleName = "member" - ViewerRoleName = "viewer" - EditorRoleName = "editor" - AdminRoleName = "admin" - SuperAdminRoleName = "superadmin" + MemberRoleName = "member" + ViewerRoleName = "viewer" + EditorRoleName = "editor" + AdminRoleName = "admin" + SuperAdminStatus = "superadmin" ) var ( diff --git a/server/auth.go b/server/auth.go index 79a0677e8..05347fd03 100644 --- a/server/auth.go +++ b/server/auth.go @@ -197,7 +197,7 @@ func hasAuthorizedRole(u *chronograf.User, role string) bool { return true } } - case roles.SuperAdminRoleName: + case roles.SuperAdminStatus: // SuperAdmins should have been authorized before this. // This is only meant to restrict access for non-superadmins. return false diff --git a/server/mux.go b/server/mux.go index 7e7c2c273..94ec2e95a 100644 --- a/server/mux.go +++ b/server/mux.go @@ -99,7 +99,7 @@ func NewMux(opts MuxOpts, service Service) http.Handler { return AuthorizedUser( service.Store, opts.UseAuth, - roles.SuperAdminRoleName, + roles.SuperAdminStatus, opts.Logger, next, ) From 4df7e38779e4e436e3a2192946bb55062c1e0ba8 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Tue, 7 Nov 2017 14:56:55 -0500 Subject: [PATCH 09/11] Add clarifying comments about how roles, organization, and context are used to filter resources Signed-off-by: Michael de Sa --- server/auth.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/server/auth.go b/server/auth.go index 05347fd03..5bf493ddf 100644 --- a/server/auth.go +++ b/server/auth.go @@ -68,6 +68,16 @@ func AuthorizedUser( unknownErrorWithMessage(w, err, logger) return } + // To access resources (servers, sources, databases, layouts) within a DataStore, + // an organization and a role are required even if you are a super admin or are + // not using auth. Every user's current organization is set on context to filter + // the resources accessed within a DataStore, including for super admin or when + // not using auth. In this way, a DataStore can treat all requests the same, + // including those from a super admin and when not using auth. + // + // As for roles, in the case of super admin or when not using auth, the user's + // role on context (though not on their JWT or user) is set to be admin. In order + // to access all resources belonging to their current organization. ctx = context.WithValue(ctx, organizations.ContextKey, fmt.Sprintf("%d", defaultOrg.ID)) ctx = context.WithValue(ctx, roles.ContextKey, roles.AdminRoleName) r = r.WithContext(ctx) @@ -122,7 +132,16 @@ func AuthorizedUser( ctx = context.WithValue(ctx, organizations.ContextKey, p.Organization) serverCtx := context.WithValue(ctx, SuperAdminKey, true) - // the DataStore expects that the roles context key be set for future calls + // To access resources (servers, sources, databases, layouts) within a DataStore, + // an organization and a role are required even if you are a super admin or are + // not using auth. Every user's current organization is set on context to filter + // the resources accessed within a DataStore, including for super admin or when + // not using auth. In this way, a DataStore can treat all requests the same, + // including those from a super admin and when not using auth. + // + // As for roles, in the case of super admin or when not using auth, the user's + // role on context (though not on their JWT or user) is set to be admin. In order + // to access all resources belonging to their current organization. serverCtx = context.WithValue(serverCtx, roles.ContextKey, roles.AdminRoleName) // TODO: seems silly to look up a user twice u, err := store.Users(serverCtx).Get(serverCtx, chronograf.UserQuery{ From 85329d0bc6cf5689a77529cbd387d7aa4a580f59 Mon Sep 17 00:00:00 2001 From: Michael de Sa Date: Tue, 7 Nov 2017 14:57:37 -0500 Subject: [PATCH 10/11] Remove super admin from store/sources tests Signed-off-by: Jared Scheib --- server/stores_test.go | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/server/stores_test.go b/server/stores_test.go index 50f162517..6fdfa3cb6 100644 --- a/server/stores_test.go +++ b/server/stores_test.go @@ -16,7 +16,6 @@ func TestStore_SourcesGet(t *testing.T) { SourcesStore chronograf.SourcesStore } type args struct { - superAdmin bool organization string role string id int @@ -261,7 +260,7 @@ func TestStore_SourcesGet(t *testing.T) { }, }, { - name: "Get source as super admin", + name: "No organization or role set on context", fields: fields{ SourcesStore: &mocks.SourcesStore{ GetF: func(ctx context.Context, id int) (chronograf.Source, error) { @@ -274,22 +273,13 @@ func TestStore_SourcesGet(t *testing.T) { }, }, }, - args: args{ - organization: "0", - role: "viewer", - superAdmin: true, - }, + args: args{}, wants: wants{ - source: chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "viewer", - }, + err: true, }, }, { - name: "Get source as super admin - no organization or role", + name: "Get source as viewer - no organization specified on context", fields: fields{ SourcesStore: &mocks.SourcesStore{ GetF: func(ctx context.Context, id int) (chronograf.Source, error) { @@ -303,14 +293,14 @@ func TestStore_SourcesGet(t *testing.T) { }, }, args: args{ - superAdmin: true, + role: "viewer", }, wants: wants{ err: true, }, }, { - name: "Get source as super admin - no role", + name: "Get source as editor - no organization specified on context", fields: fields{ SourcesStore: &mocks.SourcesStore{ GetF: func(ctx context.Context, id int) (chronograf.Source, error) { @@ -324,15 +314,14 @@ func TestStore_SourcesGet(t *testing.T) { }, }, args: args{ - superAdmin: true, - organization: "0", + role: "editor", }, wants: wants{ err: true, }, }, { - name: "Get source as super admin - no organization", + name: "Get source as admin - no organization specified on context", fields: fields{ SourcesStore: &mocks.SourcesStore{ GetF: func(ctx context.Context, id int) (chronograf.Source, error) { @@ -346,8 +335,7 @@ func TestStore_SourcesGet(t *testing.T) { }, }, args: args{ - superAdmin: true, - role: "viewer", + role: "admin", }, wants: wants{ err: true, @@ -363,10 +351,6 @@ func TestStore_SourcesGet(t *testing.T) { ctx := context.Background() - if tt.args.superAdmin { - ctx = context.WithValue(ctx, SuperAdminKey, true) - } - if tt.args.organization != "" { ctx = context.WithValue(ctx, organizations.ContextKey, tt.args.organization) } From 5062773e4e1ecbba8b20785e5edfef001aa23341 Mon Sep 17 00:00:00 2001 From: Michael de Sa Date: Tue, 7 Nov 2017 15:02:31 -0500 Subject: [PATCH 11/11] Add verification that user does not have more than one role in org Signed-off-by: Jared Scheib --- server/auth.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/auth.go b/server/auth.go index 5bf493ddf..5c2ca3e80 100644 --- a/server/auth.go +++ b/server/auth.go @@ -175,6 +175,12 @@ func AuthorizedUser( } if hasAuthorizedRole(u, role) { + if len(u.Roles) != 1 { + msg := `User %d has too many role in organization. User: %#v.Please report this log at https://github.com/influxdata/chronograf/issues/new"` + log.Error(fmt.Sprint(msg, u.ID, u)) + unknownErrorWithMessage(w, fmt.Errorf("please have administrator check logs and report error"), logger) + return + } // use the first role, since there should only ever be one // for any particular organization and hasAuthorizedRole // should ensure that at least one role for the org exists