Merge pull request #2249 from influxdata/multitenancy_scoped_sources

Sources are scoped by users role in organization
multitenancy_admin_no_delete_superadmin
Jared Scheib 2017-11-07 12:05:12 -08:00 committed by GitHub
commit 355649b885
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1835 additions and 177 deletions

View File

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

View File

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

View File

@ -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 miniumum role that a user must possess to access the resource
}
message Dashboard {

View File

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

View File

@ -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 minimum role that a user must possess to access the resource.
}
// SourcesStore stores connection information for a `TimeSeries`

63
roles/roles.go Normal file
View File

@ -0,0 +1,63 @@
package roles
import (
"context"
"fmt"
"github.com/influxdata/chronograf"
)
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 {
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"
SuperAdminStatus = "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, 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, 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, Servers, and Users
AdminRole = chronograf.Role{
Name: AdminRoleName,
}
)

138
roles/sources.go Normal file
View File

@ -0,0 +1,138 @@
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 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
}
// 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)
}
// hasAuthorizedRole checks that the role provided has at least
// the minimum role required.
func hasAuthorizedRole(sourceRole, providedRole string) bool {
switch sourceRole {
case ViewerRoleName:
switch providedRole {
case ViewerRoleName, EditorRoleName, AdminRoleName:
return true
}
case EditorRoleName:
switch providedRole {
case EditorRoleName, AdminRoleName:
return true
}
case AdminRoleName:
switch providedRole {
case AdminRoleName:
return true
}
}
return false
}

489
roles/sources_test.go Normal file
View File

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

View File

@ -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
@ -67,7 +68,18 @@ 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)
next(w, r)
return
@ -120,6 +132,17 @@ func AuthorizedUser(
ctx = context.WithValue(ctx, organizations.ContextKey, p.Organization)
serverCtx := context.WithValue(ctx, SuperAdminKey, true)
// 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{
Name: &p.Subject,
@ -152,6 +175,16 @@ 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
ctx = context.WithValue(ctx, roles.ContextKey, u.Roles[0].Name)
r = r.WithContext(ctx)
next(w, r)
return
@ -168,28 +201,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.SuperAdminStatus:
// SuperAdmins should have been authorized before this.
// This is only meant to restrict access for non-superadmins.
return false

View File

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

View File

@ -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 {
@ -219,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)
@ -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),
},

View File

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

View File

@ -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.SuperAdminStatus,
opts.Logger,
next,
)

View File

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

View File

@ -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 {
@ -259,6 +260,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 +314,10 @@ 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 == "" {
s.Role = roles.ViewerRoleName
}
return nil
}

View File

@ -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,26 @@ 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 {
case roles.MemberRoleName, roles.ViewerRoleName, roles.EditorRoleName, roles.AdminRoleName:
return role, true
default:
return "", false
}
}
type superAdminKey string
// SuperAdminKey is the context key for retrieving is the context
@ -75,7 +96,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{}

372
server/stores_test.go Normal file
View File

@ -0,0 +1,372 @@
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 {
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 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{
organization: "0",
role: "viewer",
},
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{
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: "No organization or role set on 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: "viewer",
}, nil
},
},
},
args: args{},
wants: wants{
err: true,
},
},
{
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) {
return chronograf.Source{
ID: 1,
Name: "my sweet name",
Organization: "0",
Role: "viewer",
}, nil
},
},
},
args: args{
role: "viewer",
},
wants: wants{
err: true,
},
},
{
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) {
return chronograf.Source{
ID: 1,
Name: "my sweet name",
Organization: "0",
Role: "viewer",
}, nil
},
},
},
args: args{
role: "editor",
},
wants: wants{
err: true,
},
},
{
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) {
return chronograf.Source{
ID: 1,
Name: "my sweet name",
Organization: "0",
Role: "viewer",
}, nil
},
},
},
args: args{
role: "admin",
},
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.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)
}
})
}
}

View File

@ -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', and 'admin'", 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()

View File

@ -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:
@ -176,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",
},
},
@ -197,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",
},
},
@ -228,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",
},
},
@ -249,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",
},
},
@ -502,7 +503,7 @@ func TestService_UpdateUser(t *testing.T) {
Provider: "github",
Scheme: "oauth2",
Roles: []chronograf.Role{
EditorRole,
roles.EditorRole,
},
}, nil
default:
@ -521,7 +522,7 @@ func TestService_UpdateUser(t *testing.T) {
user: &userRequest{
ID: 1336,
Roles: []chronograf.Role{
AdminRole,
roles.AdminRole,
},
},
},
@ -547,7 +548,7 @@ func TestService_UpdateUser(t *testing.T) {
Provider: "github",
Scheme: "oauth2",
Roles: []chronograf.Role{
EditorRole,
roles.EditorRole,
},
}, nil
default:
@ -567,11 +568,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 +600,7 @@ func TestService_UpdateUser(t *testing.T) {
Provider: "github",
Scheme: "oauth2",
Roles: []chronograf.Role{
EditorRole,
roles.EditorRole,
},
}, nil
default:
@ -619,11 +620,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 +652,7 @@ func TestService_UpdateUser(t *testing.T) {
Provider: "github",
Scheme: "oauth2",
Roles: []chronograf.Role{
EditorRole,
roles.EditorRole,
},
}, nil
default:
@ -672,7 +673,7 @@ func TestService_UpdateUser(t *testing.T) {
ID: 1336,
SuperAdmin: true,
Roles: []chronograf.Role{
AdminRole,
roles.AdminRole,
},
},
},
@ -698,7 +699,7 @@ func TestService_UpdateUser(t *testing.T) {
Provider: "github",
Scheme: "oauth2",
Roles: []chronograf.Role{
EditorRole,
roles.EditorRole,
},
}, nil
default:
@ -719,7 +720,7 @@ func TestService_UpdateUser(t *testing.T) {
ID: 1336,
SuperAdmin: true,
Roles: []chronograf.Role{
AdminRole,
roles.AdminRole,
},
},
},
@ -804,7 +805,7 @@ func TestService_Users(t *testing.T) {
Provider: "google",
Scheme: "oauth2",
Roles: []chronograf.Role{
EditorRole,
roles.EditorRole,
},
},
{
@ -848,7 +849,7 @@ func TestService_Users(t *testing.T) {
Provider: "google",
Scheme: "oauth2",
Roles: []chronograf.Role{
EditorRole,
roles.EditorRole,
},
},
}, nil
@ -916,7 +917,7 @@ func TestUserRequest_ValidCreate(t *testing.T) {
Provider: "auth0",
Scheme: "oauth2",
Roles: []chronograf.Role{
EditorRole,
roles.EditorRole,
},
},
},
@ -931,7 +932,7 @@ func TestUserRequest_ValidCreate(t *testing.T) {
Provider: "auth0",
Scheme: "oauth2",
Roles: []chronograf.Role{
EditorRole,
roles.EditorRole,
},
},
},
@ -946,7 +947,7 @@ func TestUserRequest_ValidCreate(t *testing.T) {
Name: "billietta",
Scheme: "oauth2",
Roles: []chronograf.Role{
EditorRole,
roles.EditorRole,
},
},
},
@ -961,7 +962,7 @@ func TestUserRequest_ValidCreate(t *testing.T) {
Name: "billietta",
Provider: "auth0",
Roles: []chronograf.Role{
EditorRole,
roles.EditorRole,
},
},
},
@ -984,7 +985,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', and 'admin'"),
},
}
@ -1021,7 +1022,7 @@ func TestUserRequest_ValidUpdate(t *testing.T) {
u: &userRequest{
ID: 1337,
Roles: []chronograf.Role{
EditorRole,
roles.EditorRole,
},
},
},