Merge pull request #2354 from influxdata/multitenancy_whitelist_only

Add Public toggle to deny authz to users not explicitly added to an Organization
pull/2330/merge
Jared Scheib 2017-11-13 20:25:23 -08:00 committed by GitHub
commit 96a362cb06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 578 additions and 200 deletions

View File

@ -514,6 +514,7 @@ func MarshalOrganization(o *chronograf.Organization) ([]byte, error) {
ID: o.ID, ID: o.ID,
Name: o.Name, Name: o.Name,
DefaultRole: o.DefaultRole, DefaultRole: o.DefaultRole,
Public: o.Public,
}) })
} }
@ -531,6 +532,7 @@ func UnmarshalOrganization(data []byte, o *chronograf.Organization) error {
o.ID = pb.ID o.ID = pb.ID
o.Name = pb.Name o.Name = pb.Name
o.DefaultRole = pb.DefaultRole o.DefaultRole = pb.DefaultRole
o.Public = pb.Public
return nil return nil
} }

View File

@ -935,6 +935,7 @@ type Organization struct {
ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
DefaultRole string `protobuf:"bytes,3,opt,name=DefaultRole,proto3" json:"DefaultRole,omitempty"` DefaultRole string `protobuf:"bytes,3,opt,name=DefaultRole,proto3" json:"DefaultRole,omitempty"`
Public bool `protobuf:"varint,4,opt,name=Public,proto3" json:"Public,omitempty"`
} }
func (m *Organization) Reset() { *m = Organization{} } func (m *Organization) Reset() { *m = Organization{} }
@ -963,6 +964,13 @@ func (m *Organization) GetDefaultRole() string {
return "" return ""
} }
func (m *Organization) GetPublic() bool {
if m != nil {
return m.Public
}
return true
}
func init() { func init() {
proto.RegisterType((*Source)(nil), "internal.Source") proto.RegisterType((*Source)(nil), "internal.Source")
proto.RegisterType((*Dashboard)(nil), "internal.Dashboard") proto.RegisterType((*Dashboard)(nil), "internal.Dashboard")
@ -985,77 +993,78 @@ func init() {
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
var fileDescriptorInternal = []byte{ var fileDescriptorInternal = []byte{
// 1148 bytes of a gzipped FileDescriptorProto // 1166 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0xcf, 0x8e, 0xe3, 0xc4, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0x5f, 0x8f, 0xdb, 0x44,
0x13, 0x56, 0xc7, 0x71, 0x12, 0x57, 0x66, 0xf7, 0xf7, 0x53, 0x6b, 0xc5, 0x9a, 0x45, 0x42, 0xc1, 0x10, 0x97, 0xe3, 0x38, 0xb1, 0x27, 0x77, 0x05, 0xad, 0x2a, 0x6a, 0x8a, 0x84, 0x82, 0x55, 0xa4,
0x02, 0x29, 0x48, 0xec, 0x80, 0x76, 0x85, 0x84, 0x38, 0x20, 0x65, 0x26, 0x68, 0x35, 0xec, 0xbf, 0x20, 0xd1, 0x03, 0xb5, 0x42, 0x42, 0x3c, 0x20, 0xe5, 0x2e, 0xa8, 0x3a, 0xfa, 0xef, 0xd8, 0xdc,
0xa1, 0x33, 0xb3, 0x9c, 0xd0, 0xaa, 0xe3, 0x54, 0x12, 0x6b, 0x9d, 0xd8, 0xb4, 0xed, 0x99, 0x98, 0x5d, 0x9f, 0x50, 0xb5, 0x71, 0x26, 0x89, 0x55, 0x27, 0x36, 0x6b, 0xfb, 0x2e, 0xee, 0xb7, 0x41,
0xb7, 0x41, 0xe2, 0xc4, 0x11, 0x71, 0x47, 0xe2, 0x84, 0xf6, 0x41, 0x78, 0x0e, 0x54, 0xdd, 0x6d, 0xe2, 0x89, 0x47, 0xc4, 0x3b, 0x12, 0x4f, 0xa8, 0x1f, 0x84, 0xcf, 0x81, 0x66, 0x77, 0xed, 0x38,
0xc7, 0xd9, 0x84, 0xd5, 0x5c, 0xe0, 0xd6, 0x5f, 0x55, 0x77, 0x75, 0x57, 0xd5, 0x57, 0x9f, 0x1a, 0x4d, 0xa8, 0xee, 0x05, 0xde, 0xf6, 0x37, 0xb3, 0x3b, 0xbb, 0x33, 0xf3, 0x9b, 0x9f, 0x16, 0x6e,
0x6e, 0x47, 0xeb, 0x1c, 0xd5, 0x5a, 0xc6, 0xc7, 0xa9, 0x4a, 0xf2, 0x84, 0xf7, 0x2a, 0x1c, 0xfc, 0x45, 0xab, 0x1c, 0xe5, 0x4a, 0xc4, 0x47, 0xa9, 0x4c, 0xf2, 0x84, 0xb9, 0x15, 0x0e, 0xfe, 0x6e,
0xd5, 0x82, 0xce, 0x24, 0x29, 0x54, 0x88, 0xfc, 0x36, 0xb4, 0xce, 0xc6, 0x3e, 0x1b, 0xb0, 0xa1, 0x41, 0x67, 0x9c, 0x14, 0x32, 0x44, 0x76, 0x0b, 0x5a, 0xa7, 0x23, 0xdf, 0xea, 0x5b, 0x03, 0x9b,
0x23, 0x5a, 0x67, 0x63, 0xce, 0xa1, 0xfd, 0x4c, 0xae, 0xd0, 0x6f, 0x0d, 0xd8, 0xd0, 0x13, 0x7a, 0xb7, 0x4e, 0x47, 0x8c, 0x41, 0xfb, 0x99, 0x58, 0xa2, 0xdf, 0xea, 0x5b, 0x03, 0x8f, 0xab, 0x35,
0x4d, 0xb6, 0x8b, 0x32, 0x45, 0xdf, 0x31, 0x36, 0x5a, 0xf3, 0x7b, 0xd0, 0xbb, 0xcc, 0x28, 0xda, 0xd9, 0xce, 0xcb, 0x14, 0x7d, 0x5b, 0xdb, 0x68, 0xcd, 0xee, 0x82, 0x7b, 0x91, 0x51, 0xb4, 0x25,
0x0a, 0xfd, 0xb6, 0xb6, 0xd7, 0x98, 0x7c, 0xe7, 0x32, 0xcb, 0xae, 0x13, 0x35, 0xf3, 0x5d, 0xe3, 0xfa, 0x6d, 0x65, 0xaf, 0x31, 0xf9, 0xce, 0x44, 0x96, 0x5d, 0x27, 0x72, 0xea, 0x3b, 0xda, 0x57,
0xab, 0x30, 0xff, 0x3f, 0x38, 0x97, 0xe2, 0x89, 0xdf, 0xd1, 0x66, 0x5a, 0x72, 0x1f, 0xba, 0x63, 0x61, 0xf6, 0x3e, 0xd8, 0x17, 0xfc, 0x89, 0xdf, 0x51, 0x66, 0x5a, 0x32, 0x1f, 0xba, 0x23, 0x9c,
0x9c, 0xcb, 0x22, 0xce, 0xfd, 0xee, 0x80, 0x0d, 0x7b, 0xa2, 0x82, 0x14, 0xe7, 0x02, 0x63, 0x5c, 0x89, 0x22, 0xce, 0xfd, 0x6e, 0xdf, 0x1a, 0xb8, 0xbc, 0x82, 0x14, 0xe7, 0x1c, 0x63, 0x9c, 0x4b,
0x28, 0x39, 0xf7, 0x7b, 0x26, 0x4e, 0x85, 0xf9, 0x31, 0xf0, 0xb3, 0x75, 0x86, 0x61, 0xa1, 0x70, 0x31, 0xf3, 0x5d, 0x1d, 0xa7, 0xc2, 0xec, 0x08, 0xd8, 0xe9, 0x2a, 0xc3, 0xb0, 0x90, 0x38, 0x7e,
0xf2, 0x2a, 0x4a, 0x5f, 0xa0, 0x8a, 0xe6, 0xa5, 0xef, 0xe9, 0x00, 0x07, 0x3c, 0x74, 0xcb, 0x53, 0x15, 0xa5, 0x97, 0x28, 0xa3, 0x59, 0xe9, 0x7b, 0x2a, 0xc0, 0x1e, 0x0f, 0xdd, 0xf2, 0x14, 0x73,
0xcc, 0x25, 0xdd, 0x0d, 0x3a, 0x54, 0x05, 0x79, 0x00, 0x47, 0x93, 0xa5, 0x54, 0x38, 0x9b, 0x60, 0x41, 0x77, 0x83, 0x0a, 0x55, 0x41, 0x16, 0xc0, 0xc1, 0x78, 0x21, 0x24, 0x4e, 0xc7, 0x18, 0x4a,
0xa8, 0x30, 0xf7, 0xfb, 0xda, 0xbd, 0x63, 0xa3, 0x3d, 0xcf, 0xd5, 0x42, 0xae, 0xa3, 0x1f, 0x65, 0xcc, 0xfd, 0x9e, 0x72, 0x6f, 0xd9, 0x68, 0xcf, 0x73, 0x39, 0x17, 0xab, 0xe8, 0xb5, 0xc8, 0xa3,
0x1e, 0x25, 0x6b, 0xff, 0xc8, 0xec, 0x69, 0xda, 0xa8, 0x4a, 0x22, 0x89, 0xd1, 0xbf, 0x65, 0xaa, 0x64, 0xe5, 0x1f, 0xe8, 0x3d, 0x4d, 0x1b, 0x55, 0x89, 0x27, 0x31, 0xfa, 0x87, 0xba, 0x4a, 0xb4,
0x44, 0xeb, 0xe0, 0x37, 0x06, 0xde, 0x58, 0x66, 0xcb, 0x69, 0x22, 0xd5, 0xec, 0x46, 0xb5, 0xbe, 0x0e, 0x7e, 0xb7, 0xc0, 0x1b, 0x89, 0x6c, 0x31, 0x49, 0x84, 0x9c, 0xde, 0xa8, 0xd6, 0xf7, 0xc1,
0x0f, 0x6e, 0x88, 0x71, 0x9c, 0xf9, 0xce, 0xc0, 0x19, 0xf6, 0x1f, 0xdc, 0x3d, 0xae, 0x9b, 0x58, 0x09, 0x31, 0x8e, 0x33, 0xdf, 0xee, 0xdb, 0x83, 0xde, 0x83, 0x3b, 0x47, 0x75, 0x13, 0xeb, 0x38,
0xc7, 0x39, 0xc5, 0x38, 0x16, 0x66, 0x17, 0xff, 0x0c, 0xbc, 0x1c, 0x57, 0x69, 0x2c, 0x73, 0xcc, 0x27, 0x18, 0xc7, 0x5c, 0xef, 0x62, 0x5f, 0x82, 0x97, 0xe3, 0x32, 0x8d, 0x45, 0x8e, 0x99, 0xdf,
0xfc, 0xb6, 0x3e, 0xc2, 0xb7, 0x47, 0x2e, 0xac, 0x4b, 0x6c, 0x37, 0xed, 0xa5, 0xe2, 0xee, 0xa7, 0x56, 0x47, 0xd8, 0xe6, 0xc8, 0xb9, 0x71, 0xf1, 0xcd, 0xa6, 0x9d, 0x54, 0x9c, 0xdd, 0x54, 0x82,
0x12, 0xfc, 0xd2, 0x82, 0x5b, 0x3b, 0xd7, 0xf1, 0x23, 0x60, 0x1b, 0xfd, 0x72, 0x57, 0xb0, 0x0d, 0x5f, 0x5b, 0x70, 0xb8, 0x75, 0x1d, 0x3b, 0x00, 0x6b, 0xad, 0x5e, 0xee, 0x70, 0x6b, 0x4d, 0xa8,
0xa1, 0x52, 0xbf, 0xda, 0x15, 0xac, 0x24, 0x74, 0xad, 0xb9, 0xe1, 0x0a, 0x76, 0x4d, 0x68, 0xa9, 0x54, 0xaf, 0x76, 0xb8, 0x55, 0x12, 0xba, 0x56, 0xdc, 0x70, 0xb8, 0x75, 0x4d, 0x68, 0xa1, 0x18,
0x19, 0xe1, 0x0a, 0xb6, 0xe4, 0x1f, 0x43, 0xf7, 0x87, 0x02, 0x55, 0x84, 0x99, 0xef, 0xea, 0xd7, 0xe1, 0x70, 0x6b, 0xc1, 0x3e, 0x83, 0xee, 0x4f, 0x05, 0xca, 0x08, 0x33, 0xdf, 0x51, 0xaf, 0x7b,
0xfd, 0x6f, 0xfb, 0xba, 0x6f, 0x0b, 0x54, 0xa5, 0xa8, 0xfc, 0x54, 0x0d, 0xcd, 0x26, 0x43, 0x0d, 0x6f, 0xf3, 0xba, 0x1f, 0x0a, 0x94, 0x25, 0xaf, 0xfc, 0x54, 0x0d, 0xc5, 0x26, 0x4d, 0x0d, 0xb5,
0xbd, 0x26, 0x5b, 0x4e, 0xcc, 0xeb, 0x1a, 0x1b, 0xad, 0x6d, 0x15, 0x0d, 0x1f, 0xa8, 0x8a, 0x9f, 0x26, 0x5b, 0x4e, 0xcc, 0xeb, 0x6a, 0x1b, 0xad, 0x4d, 0x15, 0x35, 0x1f, 0xa8, 0x8a, 0x5f, 0x41,
0x43, 0x5b, 0x6e, 0x30, 0xf3, 0x3d, 0x1d, 0xff, 0x83, 0x7f, 0x28, 0xd8, 0xf1, 0x68, 0x83, 0xd9, 0x5b, 0xac, 0x31, 0xf3, 0x3d, 0x15, 0xff, 0x93, 0x7f, 0x29, 0xd8, 0xd1, 0x70, 0x8d, 0xd9, 0x77,
0xd7, 0xeb, 0x5c, 0x95, 0x42, 0x6f, 0xbf, 0xf7, 0x08, 0xbc, 0xda, 0x44, 0xac, 0x7c, 0x85, 0xa5, 0xab, 0x5c, 0x96, 0x5c, 0x6d, 0xbf, 0xfb, 0x08, 0xbc, 0xda, 0x44, 0xac, 0x7c, 0x85, 0xa5, 0x4a,
0x4e, 0xd0, 0x13, 0xb4, 0xe4, 0x1f, 0x82, 0x7b, 0x25, 0xe3, 0xc2, 0x34, 0xa7, 0xff, 0xe0, 0xf6, 0xd0, 0xe3, 0xb4, 0x64, 0xf7, 0xc0, 0xb9, 0x12, 0x71, 0xa1, 0x9b, 0xd3, 0x7b, 0x70, 0x6b, 0x13,
0x36, 0xec, 0x68, 0x13, 0x65, 0xc2, 0x38, 0xbf, 0x6c, 0x7d, 0xc1, 0x82, 0x5f, 0x19, 0xb4, 0xc9, 0x76, 0xb8, 0x8e, 0x32, 0xae, 0x9d, 0xdf, 0xb4, 0xbe, 0xb6, 0x82, 0xdf, 0x2c, 0x68, 0x93, 0x8d,
0x46, 0x95, 0x8d, 0x71, 0x21, 0xc3, 0xf2, 0x24, 0x29, 0xd6, 0xb3, 0xcc, 0x67, 0x03, 0x67, 0xe8, 0x2a, 0x1b, 0xe3, 0x5c, 0x84, 0xe5, 0x71, 0x52, 0xac, 0xa6, 0x99, 0x6f, 0xf5, 0xed, 0x81, 0xcd,
0x88, 0x1d, 0x1b, 0x7f, 0x07, 0x3a, 0x53, 0xe3, 0x6d, 0x0d, 0x9c, 0xa1, 0x27, 0x2c, 0xe2, 0x77, 0xb7, 0x6c, 0xec, 0x03, 0xe8, 0x4c, 0xb4, 0xb7, 0xd5, 0xb7, 0x07, 0x1e, 0x37, 0x88, 0xdd, 0x06,
0xc0, 0x8d, 0xe5, 0x14, 0x63, 0x3b, 0x63, 0x06, 0xd0, 0xee, 0x54, 0xe1, 0x3c, 0xda, 0xd8, 0x11, 0x27, 0x16, 0x13, 0x8c, 0xcd, 0x8c, 0x69, 0x40, 0xbb, 0x53, 0x89, 0xb3, 0x68, 0x6d, 0x46, 0xcc,
0xb3, 0x88, 0xec, 0x59, 0x31, 0x27, 0xbb, 0xe9, 0x9e, 0x45, 0x54, 0xae, 0xa9, 0xcc, 0xea, 0x12, 0x20, 0xb2, 0x67, 0xc5, 0x8c, 0xec, 0xba, 0x7b, 0x06, 0x51, 0xb9, 0x26, 0x22, 0xab, 0x4b, 0x48,
0xd2, 0x9a, 0x22, 0x67, 0xa1, 0x8c, 0xab, 0x1a, 0x1a, 0x10, 0xfc, 0xce, 0x68, 0xb6, 0x0c, 0x27, 0x6b, 0x8a, 0x9c, 0x85, 0x22, 0xae, 0x6a, 0xa8, 0x41, 0xf0, 0x87, 0x45, 0xb3, 0xa5, 0x39, 0xd1,
0x1a, 0xbc, 0x34, 0x15, 0x7d, 0x17, 0x7a, 0xc4, 0x97, 0x97, 0x57, 0x52, 0x59, 0x6e, 0x76, 0x09, 0xe0, 0xa5, 0xae, 0xe8, 0x87, 0xe0, 0x12, 0x5f, 0x5e, 0x5e, 0x09, 0x69, 0xb8, 0xd9, 0x25, 0x7c,
0xbf, 0x90, 0x8a, 0x7f, 0x0a, 0x1d, 0x9d, 0xf9, 0x01, 0x7e, 0x56, 0xe1, 0x5e, 0x90, 0x5f, 0xd8, 0x29, 0x24, 0xfb, 0x02, 0x3a, 0x2a, 0xf3, 0x3d, 0xfc, 0xac, 0xc2, 0x5d, 0x92, 0x9f, 0x9b, 0x6d,
0x6d, 0x75, 0x07, 0xdb, 0x8d, 0x0e, 0xd6, 0xc9, 0xba, 0xcd, 0x64, 0xef, 0x83, 0x4b, 0x54, 0x28, 0x75, 0x07, 0xdb, 0x8d, 0x0e, 0xd6, 0xc9, 0x3a, 0xcd, 0x64, 0xef, 0x83, 0x43, 0x54, 0x28, 0xd5,
0xf5, 0xeb, 0x0f, 0x46, 0x36, 0x84, 0x31, 0xbb, 0x82, 0x4b, 0xb8, 0xb5, 0x73, 0x63, 0x7d, 0x13, 0xeb, 0xf7, 0x46, 0xd6, 0x84, 0xd1, 0xbb, 0x82, 0x0b, 0x38, 0xdc, 0xba, 0xb1, 0xbe, 0xc9, 0xda,
0xdb, 0xbd, 0x69, 0xdb, 0x45, 0xcf, 0x76, 0x8d, 0x74, 0x25, 0xc3, 0x18, 0xc3, 0x1c, 0x67, 0xba, 0xbe, 0x69, 0xd3, 0x45, 0xcf, 0x74, 0x8d, 0x74, 0x25, 0xc3, 0x18, 0xc3, 0x1c, 0xa7, 0xaa, 0xde,
0xde, 0x3d, 0x51, 0xe3, 0xe0, 0x27, 0xb6, 0x8d, 0xab, 0xef, 0x23, 0xe5, 0x08, 0x93, 0xd5, 0x4a, 0x2e, 0xaf, 0x71, 0xf0, 0xb3, 0xb5, 0x89, 0xab, 0xee, 0x23, 0xe5, 0x08, 0x93, 0xe5, 0x52, 0xac,
0xae, 0x67, 0x36, 0x74, 0x05, 0xa9, 0x6e, 0xb3, 0xa9, 0x0d, 0xdd, 0x9a, 0x4d, 0x09, 0xab, 0xd4, 0xa6, 0x26, 0x74, 0x05, 0xa9, 0x6e, 0xd3, 0x89, 0x09, 0xdd, 0x9a, 0x4e, 0x08, 0xcb, 0xd4, 0x74,
0x76, 0xb0, 0xa5, 0x52, 0x3e, 0x80, 0xfe, 0x0a, 0x65, 0x56, 0x28, 0x5c, 0xe1, 0x3a, 0xb7, 0x25, 0xb0, 0x25, 0x53, 0xd6, 0x87, 0xde, 0x12, 0x45, 0x56, 0x48, 0x5c, 0xe2, 0x2a, 0x37, 0x25, 0x68,
0x68, 0x9a, 0xf8, 0x5d, 0xe8, 0xe6, 0x72, 0xf1, 0x92, 0xb8, 0x67, 0x3b, 0x99, 0xcb, 0xc5, 0x63, 0x9a, 0xd8, 0x1d, 0xe8, 0xe6, 0x62, 0xfe, 0x92, 0xb8, 0x67, 0x3a, 0x99, 0x8b, 0xf9, 0x63, 0x2c,
0x2c, 0xf9, 0x7b, 0xe0, 0xcd, 0x23, 0x8c, 0x67, 0xda, 0x65, 0xda, 0xd9, 0xd3, 0x86, 0xc7, 0x58, 0xd9, 0x47, 0xe0, 0xcd, 0x22, 0x8c, 0xa7, 0xca, 0xa5, 0xdb, 0xe9, 0x2a, 0xc3, 0x63, 0x2c, 0x83,
0x06, 0x7f, 0x32, 0xe8, 0x4c, 0x50, 0x5d, 0xa1, 0xba, 0x91, 0xa4, 0x34, 0xa5, 0xda, 0x79, 0x8b, 0xbf, 0x2c, 0xe8, 0x8c, 0x51, 0x5e, 0xa1, 0xbc, 0x91, 0xa4, 0x34, 0xa5, 0xda, 0x7e, 0x87, 0x54,
0x54, 0xb7, 0x0f, 0x4b, 0xb5, 0xbb, 0x95, 0xea, 0x3b, 0xe0, 0x4e, 0x54, 0x78, 0x36, 0xd6, 0x2f, 0xb7, 0xf7, 0x4b, 0xb5, 0xb3, 0x91, 0xea, 0xdb, 0xe0, 0x8c, 0x65, 0x78, 0x3a, 0x52, 0x2f, 0xb2,
0x72, 0x84, 0x01, 0xc4, 0xc6, 0x51, 0x98, 0x47, 0x57, 0x68, 0xf5, 0xdb, 0xa2, 0x3d, 0xa5, 0xe9, 0xb9, 0x06, 0xc4, 0xc6, 0x61, 0x98, 0x47, 0x57, 0x68, 0xf4, 0xdb, 0xa0, 0x1d, 0xa5, 0x71, 0xf7,
0x1d, 0x50, 0x9a, 0x3f, 0x18, 0x74, 0x9e, 0xc8, 0x32, 0x29, 0xf2, 0x3d, 0x16, 0x0e, 0xa0, 0x3f, 0x28, 0xcd, 0x9f, 0x16, 0x74, 0x9e, 0x88, 0x32, 0x29, 0xf2, 0x1d, 0x16, 0xf6, 0xa1, 0x37, 0x4c,
0x4a, 0xd3, 0x38, 0x0a, 0xcd, 0x69, 0x93, 0x51, 0xd3, 0x44, 0x3b, 0x9e, 0x36, 0xea, 0x6b, 0x72, 0xd3, 0x38, 0x0a, 0xf5, 0x69, 0x9d, 0x51, 0xd3, 0x44, 0x3b, 0x9e, 0x36, 0xea, 0xab, 0x73, 0x6b,
0x6b, 0x9a, 0x68, 0x8a, 0x4f, 0xb5, 0x9a, 0x1a, 0x69, 0x6c, 0x4c, 0xb1, 0x11, 0x51, 0xed, 0xa4, 0x9a, 0x68, 0x8a, 0x4f, 0x94, 0x9a, 0x6a, 0x69, 0x6c, 0x4c, 0xb1, 0x16, 0x51, 0xe5, 0xa4, 0x22,
0x22, 0x8c, 0x8a, 0x3c, 0x99, 0xc7, 0xc9, 0xb5, 0xce, 0xb6, 0x27, 0x6a, 0xbc, 0x97, 0x44, 0xe7, 0x0c, 0x8b, 0x3c, 0x99, 0xc5, 0xc9, 0xb5, 0xca, 0xd6, 0xe5, 0x35, 0xde, 0x49, 0xa2, 0xb3, 0x27,
0x40, 0x12, 0xaf, 0x5b, 0xd0, 0xfe, 0xaf, 0x54, 0xf2, 0x08, 0x58, 0x64, 0x1f, 0xc1, 0xa2, 0x5a, 0x89, 0x37, 0x2d, 0x68, 0xff, 0x5f, 0x2a, 0x79, 0x00, 0x56, 0x64, 0x1e, 0x61, 0x45, 0xb5, 0x66,
0x33, 0xbb, 0x0d, 0xcd, 0xf4, 0xa1, 0x5b, 0x2a, 0xb9, 0x5e, 0x60, 0xe6, 0xf7, 0xb4, 0x02, 0x55, 0x76, 0x1b, 0x9a, 0xe9, 0x43, 0xb7, 0x94, 0x62, 0x35, 0xc7, 0xcc, 0x77, 0x95, 0x02, 0x55, 0x50,
0x50, 0x7b, 0xf4, 0xac, 0x19, 0xb1, 0xf4, 0x44, 0x05, 0xeb, 0xd9, 0x81, 0xc6, 0xec, 0x7c, 0x62, 0x79, 0xd4, 0xac, 0x69, 0xb1, 0xf4, 0x78, 0x05, 0xeb, 0xd9, 0x81, 0xc6, 0xec, 0x7c, 0x6e, 0x74,
0x75, 0xb5, 0xaf, 0x5f, 0xe4, 0xef, 0x96, 0xee, 0xdf, 0x93, 0xd3, 0xd7, 0x0c, 0xdc, 0x7a, 0xf0, 0xb5, 0xa7, 0x5e, 0xe4, 0x6f, 0x97, 0xee, 0xbf, 0x93, 0xd3, 0x37, 0x16, 0x38, 0xf5, 0xe0, 0x9d,
0x4e, 0x77, 0x07, 0xef, 0x74, 0x3b, 0x78, 0xe3, 0x93, 0x6a, 0xf0, 0xc6, 0x27, 0x84, 0xc5, 0x79, 0x6c, 0x0f, 0xde, 0xc9, 0x66, 0xf0, 0x46, 0xc7, 0xd5, 0xe0, 0x8d, 0x8e, 0x09, 0xf3, 0xb3, 0x6a,
0x35, 0x78, 0xe2, 0x9c, 0x1a, 0xfa, 0x48, 0x25, 0x45, 0x7a, 0x52, 0x9a, 0xce, 0x7b, 0xa2, 0xc6, 0xf0, 0xf8, 0x19, 0x35, 0xf4, 0x91, 0x4c, 0x8a, 0xf4, 0xb8, 0xd4, 0x9d, 0xf7, 0x78, 0x8d, 0x89,
0xc4, 0xd6, 0xef, 0x96, 0xa8, 0x6c, 0xa9, 0x3d, 0x61, 0x11, 0x71, 0xfb, 0x89, 0x16, 0x25, 0x53, 0xad, 0x2f, 0x16, 0x28, 0x4d, 0xa9, 0x3d, 0x6e, 0x10, 0x71, 0xfb, 0x89, 0x12, 0x25, 0x5d, 0x5c,
0x5c, 0x03, 0xf8, 0x47, 0xe0, 0x0a, 0x2a, 0x9e, 0xae, 0xf0, 0x4e, 0x5f, 0xb4, 0x59, 0x18, 0x2f, 0x0d, 0xd8, 0xa7, 0xe0, 0x70, 0x2a, 0x9e, 0xaa, 0xf0, 0x56, 0x5f, 0x94, 0x99, 0x6b, 0x2f, 0x05,
0x05, 0x35, 0xff, 0x29, 0x4b, 0x72, 0x8b, 0x82, 0x87, 0xf6, 0x38, 0x45, 0xbf, 0x4c, 0x53, 0x54, 0xd5, 0xff, 0x29, 0x43, 0x72, 0x83, 0x82, 0x87, 0xe6, 0x38, 0x45, 0xbf, 0x48, 0x53, 0x94, 0x66,
0x76, 0x54, 0x0d, 0xd0, 0x77, 0x26, 0xd7, 0x68, 0x54, 0xd6, 0x11, 0x06, 0x04, 0xdf, 0x83, 0x37, 0x54, 0x35, 0x50, 0x77, 0x26, 0xd7, 0xa8, 0x55, 0xd6, 0xe6, 0x1a, 0x04, 0x3f, 0x82, 0x37, 0x8c,
0x8a, 0x51, 0xe5, 0xa2, 0x88, 0xf7, 0xb5, 0x99, 0x43, 0xfb, 0x9b, 0xc9, 0xf3, 0x67, 0xd5, 0x80, 0x51, 0xe6, 0xbc, 0x88, 0x77, 0xb5, 0x99, 0x41, 0xfb, 0xfb, 0xf1, 0xf3, 0x67, 0xd5, 0x80, 0xd3,
0xd3, 0x7a, 0x3b, 0x96, 0xce, 0x1b, 0x63, 0xf9, 0x58, 0xa6, 0xf2, 0x6c, 0xac, 0x79, 0xe6, 0x08, 0x7a, 0x33, 0x96, 0xf6, 0x5b, 0x63, 0xf9, 0x58, 0xa4, 0xe2, 0x74, 0xa4, 0x78, 0x66, 0x73, 0x83,
0x8b, 0x82, 0x9f, 0x19, 0xb4, 0x69, 0xfe, 0x1b, 0xa1, 0xdb, 0x6f, 0xd3, 0x8e, 0x73, 0x95, 0x5c, 0x82, 0x5f, 0x2c, 0x68, 0xd3, 0xfc, 0x37, 0x42, 0xb7, 0xdf, 0xa5, 0x1d, 0x67, 0x32, 0xb9, 0x8a,
0x45, 0x33, 0x54, 0x95, 0x76, 0x54, 0x58, 0x27, 0x1d, 0x2e, 0xb1, 0xfe, 0x00, 0x5a, 0x44, 0xbd, 0xa6, 0x28, 0x2b, 0xed, 0xa8, 0xb0, 0x4a, 0x3a, 0x5c, 0x60, 0xfd, 0x01, 0x34, 0x88, 0x7a, 0x4d,
0xa6, 0xcf, 0x4f, 0xc5, 0xe5, 0x46, 0xaf, 0xc9, 0x2c, 0x8c, 0x93, 0xbf, 0x0f, 0x30, 0x29, 0x52, 0x9f, 0x9f, 0x8a, 0xcb, 0x8d, 0x5e, 0x93, 0x99, 0x6b, 0x27, 0xfb, 0x18, 0x60, 0x5c, 0xa4, 0x28,
0x54, 0xa3, 0xd9, 0x2a, 0x32, 0x63, 0xd5, 0x13, 0x0d, 0x4b, 0xf0, 0x95, 0xf9, 0x4e, 0xed, 0x0d, 0x87, 0xd3, 0x65, 0xa4, 0xc7, 0xca, 0xe5, 0x0d, 0x4b, 0xf0, 0xad, 0xfe, 0x4e, 0xed, 0x0c, 0xa0,
0x20, 0x3b, 0xfc, 0xf5, 0x7a, 0xf3, 0xe5, 0xc1, 0xc5, 0xee, 0xb9, 0x1b, 0x65, 0x3b, 0x80, 0xbe, 0xb5, 0xff, 0xeb, 0xf5, 0xf6, 0xcb, 0x83, 0xd7, 0xdb, 0xe7, 0x6e, 0x94, 0x6d, 0x1f, 0x7a, 0xe6,
0xfd, 0x7b, 0xea, 0x9f, 0x9c, 0x15, 0x94, 0x86, 0x69, 0xda, 0xd1, 0x5f, 0xe9, 0x87, 0x7f, 0x07, 0xef, 0xa9, 0x7e, 0x72, 0x46, 0x50, 0x1a, 0x26, 0x76, 0x0f, 0x0e, 0x5f, 0x2c, 0xa2, 0x1c, 0xe3,
0x00, 0x00, 0xff, 0xff, 0x90, 0x83, 0x07, 0x1e, 0x5c, 0x0b, 0x00, 0x00, 0x28, 0xcb, 0x9f, 0xaf, 0xe2, 0x52, 0xa5, 0xee, 0xf2, 0x6d, 0xe3, 0xa4, 0xa3, 0x3e, 0xdc, 0x0f,
0xff, 0x09, 0x00, 0x00, 0xff, 0xff, 0x52, 0x79, 0x4d, 0x4a, 0x82, 0x0b, 0x00, 0x00,
} }

View File

@ -146,6 +146,7 @@ message Organization {
uint64 ID = 1; // ID is the unique ID of the organization uint64 ID = 1; // ID is the unique ID of the organization
string Name = 2; // Name is the organization's name string Name = 2; // Name is the organization's name
string DefaultRole = 3; // DefaultRole is the name of the role that is the default for any users added to the organization string DefaultRole = 3; // DefaultRole is the name of the role that is the default for any users added to the organization
bool Public = 4; // Public specifies that users must be explicitly added to the organization
} }
// The following is a vim modeline, it autoconfigures vim to have the // The following is a vim modeline, it autoconfigures vim to have the

View File

@ -23,6 +23,8 @@ const (
DefaultOrganizationName string = "Default" DefaultOrganizationName string = "Default"
// DefaultOrganizationRole is the DefaultRole for the Default organization // DefaultOrganizationRole is the DefaultRole for the Default organization
DefaultOrganizationRole string = "member" DefaultOrganizationRole string = "member"
// DefaultOrganizationPublic is the Public setting for the Default organization.
DefaultOrganizationPublic bool = true
) )
// OrganizationsStore uses bolt to store and retrieve Organizations // OrganizationsStore uses bolt to store and retrieve Organizations
@ -41,6 +43,7 @@ func (s *OrganizationsStore) CreateDefault(ctx context.Context) error {
ID: DefaultOrganizationID, ID: DefaultOrganizationID,
Name: DefaultOrganizationName, Name: DefaultOrganizationName,
DefaultRole: DefaultOrganizationRole, DefaultRole: DefaultOrganizationRole,
Public: DefaultOrganizationPublic,
} }
return s.client.db.Update(func(tx *bolt.Tx) error { return s.client.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket(OrganizationsBucket) b := tx.Bucket(OrganizationsBucket)

View File

@ -171,15 +171,19 @@ func TestOrganizationsStore_All(t *testing.T) {
addFirst bool addFirst bool
}{ }{
{ {
name: "Get Organization", name: "Get Organizations",
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
orgs: []chronograf.Organization{ orgs: []chronograf.Organization{
{ {
Name: "EE - Evil Empire", Name: "EE - Evil Empire",
DefaultRole: roles.MemberRoleName,
Public: true,
}, },
{ {
Name: "The Good Place", Name: "The Good Place",
DefaultRole: roles.EditorRoleName,
Public: true,
}, },
}, },
}, },
@ -187,12 +191,17 @@ func TestOrganizationsStore_All(t *testing.T) {
{ {
Name: bolt.DefaultOrganizationName, Name: bolt.DefaultOrganizationName,
DefaultRole: bolt.DefaultOrganizationRole, DefaultRole: bolt.DefaultOrganizationRole,
Public: bolt.DefaultOrganizationPublic,
}, },
{ {
Name: "EE - Evil Empire", Name: "EE - Evil Empire",
DefaultRole: roles.MemberRoleName,
Public: true,
}, },
{ {
Name: "The Good Place", Name: "The Good Place",
DefaultRole: roles.EditorRoleName,
Public: true,
}, },
}, },
addFirst: true, addFirst: true,
@ -239,9 +248,8 @@ func TestOrganizationsStore_Update(t *testing.T) {
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
org *chronograf.Organization initial *chronograf.Organization
name string updates *chronograf.Organization
defaultRole string
} }
tests := []struct { tests := []struct {
name string name string
@ -256,10 +264,11 @@ func TestOrganizationsStore_Update(t *testing.T) {
fields: fields{}, fields: fields{},
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
org: &chronograf.Organization{ initial: &chronograf.Organization{
ID: 1234, ID: 1234,
Name: "The Okay Place", Name: "The Okay Place",
}, },
updates: &chronograf.Organization{},
}, },
wantErr: true, wantErr: true,
}, },
@ -268,10 +277,12 @@ func TestOrganizationsStore_Update(t *testing.T) {
fields: fields{}, fields: fields{},
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
org: &chronograf.Organization{ initial: &chronograf.Organization{
Name: "The Good Place", Name: "The Good Place",
}, },
name: "The Bad Place", updates: &chronograf.Organization{
Name: "The Bad Place",
},
}, },
want: &chronograf.Organization{ want: &chronograf.Organization{
Name: "The Bad Place", Name: "The Bad Place",
@ -283,10 +294,12 @@ func TestOrganizationsStore_Update(t *testing.T) {
fields: fields{}, fields: fields{},
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
org: &chronograf.Organization{ initial: &chronograf.Organization{
Name: "The Good Place", Name: "The Good Place",
}, },
defaultRole: roles.ViewerRoleName, updates: &chronograf.Organization{
DefaultRole: roles.ViewerRoleName,
},
}, },
want: &chronograf.Organization{ want: &chronograf.Organization{
Name: "The Good Place", Name: "The Good Place",
@ -299,12 +312,14 @@ func TestOrganizationsStore_Update(t *testing.T) {
fields: fields{}, fields: fields{},
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
org: &chronograf.Organization{ initial: &chronograf.Organization{
Name: "The Good Place", Name: "The Good Place",
DefaultRole: roles.AdminRoleName, DefaultRole: roles.AdminRoleName,
}, },
name: "The Bad Place", updates: &chronograf.Organization{
defaultRole: roles.ViewerRoleName, Name: "The Bad Place",
DefaultRole: roles.ViewerRoleName,
},
}, },
want: &chronograf.Organization{ want: &chronograf.Organization{
Name: "The Bad Place", Name: "The Bad Place",
@ -312,6 +327,51 @@ func TestOrganizationsStore_Update(t *testing.T) {
}, },
addFirst: true, addFirst: true,
}, },
{
name: "Update organization name, role, public",
fields: fields{},
args: args{
ctx: context.Background(),
initial: &chronograf.Organization{
Name: "The Good Place",
DefaultRole: roles.ViewerRoleName,
Public: false,
},
updates: &chronograf.Organization{
Name: "The Bad Place",
Public: true,
DefaultRole: roles.AdminRoleName,
},
},
want: &chronograf.Organization{
Name: "The Bad Place",
Public: true,
DefaultRole: roles.AdminRoleName,
},
addFirst: true,
},
{
name: "Update organization name and public",
fields: fields{},
args: args{
ctx: context.Background(),
initial: &chronograf.Organization{
Name: "The Good Place",
DefaultRole: roles.EditorRoleName,
Public: false,
},
updates: &chronograf.Organization{
Name: "The Bad Place",
Public: true,
},
},
want: &chronograf.Organization{
Name: "The Bad Place",
DefaultRole: roles.EditorRoleName,
Public: true,
},
addFirst: true,
},
{ {
name: "Update organization name - name already taken", name: "Update organization name - name already taken",
fields: fields{ fields: fields{
@ -323,10 +383,12 @@ func TestOrganizationsStore_Update(t *testing.T) {
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
org: &chronograf.Organization{ initial: &chronograf.Organization{
Name: "The Good Place", Name: "The Good Place",
}, },
name: "The Bad Place", updates: &chronograf.Organization{
Name: "The Bad Place",
},
}, },
wantErr: true, wantErr: true,
addFirst: true, addFirst: true,
@ -351,17 +413,21 @@ func TestOrganizationsStore_Update(t *testing.T) {
} }
if tt.addFirst { if tt.addFirst {
tt.args.org, err = s.Add(tt.args.ctx, tt.args.org) tt.args.initial, err = s.Add(tt.args.ctx, tt.args.initial)
} }
if tt.args.name != "" { if tt.args.updates.Name != "" {
tt.args.org.Name = tt.args.name tt.args.initial.Name = tt.args.updates.Name
} }
if tt.args.defaultRole != "" { if tt.args.updates.DefaultRole != "" {
tt.args.org.DefaultRole = tt.args.defaultRole tt.args.initial.DefaultRole = tt.args.updates.DefaultRole
} }
if err := s.Update(tt.args.ctx, tt.args.org); (err != nil) != tt.wantErr { if tt.args.updates.Public != tt.args.initial.Public {
tt.args.initial.Public = tt.args.updates.Public
}
if err := s.Update(tt.args.ctx, tt.args.initial); (err != nil) != tt.wantErr {
t.Errorf("%q. OrganizationsStore.Update() error = %v, wantErr %v", tt.name, err, tt.wantErr) t.Errorf("%q. OrganizationsStore.Update() error = %v, wantErr %v", tt.name, err, tt.wantErr)
} }
@ -370,7 +436,7 @@ func TestOrganizationsStore_Update(t *testing.T) {
continue continue
} }
got, err := s.Get(tt.args.ctx, chronograf.OrganizationQuery{Name: &tt.args.org.Name}) got, err := s.Get(tt.args.ctx, chronograf.OrganizationQuery{Name: &tt.args.initial.Name})
if err != nil { if err != nil {
t.Fatalf("failed to get organization: %v", err) t.Fatalf("failed to get organization: %v", err)
} }
@ -572,6 +638,7 @@ func TestOrganizationsStore_DefaultOrganization(t *testing.T) {
ID: bolt.DefaultOrganizationID, ID: bolt.DefaultOrganizationID,
Name: bolt.DefaultOrganizationName, Name: bolt.DefaultOrganizationName,
DefaultRole: bolt.DefaultOrganizationRole, DefaultRole: bolt.DefaultOrganizationRole,
Public: bolt.DefaultOrganizationPublic,
}, },
wantErr: false, wantErr: false,
}, },

View File

@ -786,6 +786,9 @@ type Organization struct {
Name string `json:"name"` Name string `json:"name"`
// DefaultRole is the name of the role that is the default for any users added to the organization // DefaultRole is the name of the role that is the default for any users added to the organization
DefaultRole string `json:"defaultRole,omitempty"` DefaultRole string `json:"defaultRole,omitempty"`
// Public specifies whether users must be explicitly added to the organization.
// It is currently only used by the default organization, but that may change in the future.
Public bool `json:"public"`
} }
// OrganizationQuery represents the attributes that a organization may be retrieved by. // OrganizationQuery represents the attributes that a organization may be retrieved by.

View File

@ -11,7 +11,6 @@ import (
"github.com/influxdata/chronograf" "github.com/influxdata/chronograf"
"github.com/influxdata/chronograf/oauth2" "github.com/influxdata/chronograf/oauth2"
"github.com/influxdata/chronograf/organizations" "github.com/influxdata/chronograf/organizations"
"github.com/influxdata/chronograf/roles"
) )
type meLinks struct { type meLinks struct {
@ -72,14 +71,14 @@ func getValidPrincipal(ctx context.Context) (oauth2.Principal, error) {
return p, nil return p, nil
} }
type meOrganizationRequest struct { type meRequest struct {
// Organization is the OrganizationID // Organization is the OrganizationID
Organization string `json:"organization"` Organization string `json:"organization"`
} }
// MeOrganization changes the user's current organization on the JWT and responds // UpdateMe changes the user's current organization on the JWT and responds
// with the same semantics as Me // with the same semantics as Me
func (s *Service) MeOrganization(auth oauth2.Authenticator) func(http.ResponseWriter, *http.Request) { func (s *Service) UpdateMe(auth oauth2.Authenticator) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
serverCtx := serverContext(ctx) serverCtx := serverContext(ctx)
@ -89,7 +88,7 @@ func (s *Service) MeOrganization(auth oauth2.Authenticator) func(http.ResponseWr
Error(w, http.StatusForbidden, "invalid principal", s.Logger) Error(w, http.StatusForbidden, "invalid principal", s.Logger)
return return
} }
var req meOrganizationRequest var req meRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
invalidJSON(w, s.Logger) invalidJSON(w, s.Logger)
return return
@ -134,7 +133,8 @@ func (s *Service) MeOrganization(auth oauth2.Authenticator) func(http.ResponseWr
Scheme: &scheme, Scheme: &scheme,
}) })
if err == chronograf.ErrUserNotFound { if err == chronograf.ErrUserNotFound {
Error(w, http.StatusBadRequest, err.Error(), s.Logger) // Since a user is not a part of this organization, we should tell them that they are Forbidden (403) from accessing this resource
Error(w, http.StatusForbidden, err.Error(), s.Logger)
return return
} }
if err != nil { if err != nil {
@ -199,7 +199,18 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) {
return return
} }
defaultOrg, err := s.Store.Organizations(serverCtx).DefaultOrganization(serverCtx)
if err != nil {
unknownErrorWithMessage(w, err, s.Logger)
return
}
if usr != nil { if usr != nil {
// If the default org is private and the user has no roles, they should not have access
if !defaultOrg.Public && len(usr.Roles) == 0 {
Error(w, http.StatusForbidden, "This organization is private. To gain access, you must be explicitly added by an administrator.", s.Logger)
return
}
orgID, err := parseOrganizationID(p.Organization) orgID, err := parseOrganizationID(p.Organization)
if err != nil { if err != nil {
unknownErrorWithMessage(w, err, s.Logger) unknownErrorWithMessage(w, err, s.Logger)
@ -210,14 +221,13 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) {
unknownErrorWithMessage(w, err, s.Logger) unknownErrorWithMessage(w, err, s.Logger)
return return
} }
defaultOrgID := fmt.Sprintf("%d", defaultOrg.ID)
// If a user was added via the API, they might not yet be a member of the default organization // If a user was added via the API, they might not yet be a member of the default organization
// Here we check to verify that they are a user in the default organization // Here we check to verify that they are a user in the default organization
// TODO(desa): when https://github.com/influxdata/chronograf/pull/2219 is merge, refactor this to use if !hasRoleInDefaultOrganization(usr, defaultOrgID) {
// the default organization logic rather than hard coding valies here.
if !hasRoleInDefaultOrganization(usr) {
usr.Roles = append(usr.Roles, chronograf.Role{ usr.Roles = append(usr.Roles, chronograf.Role{
Organization: "0", Organization: defaultOrgID,
Name: roles.MemberRoleName, Name: defaultOrg.DefaultRole,
}) })
if err := s.Store.Users(serverCtx).Update(serverCtx, usr); err != nil { if err := s.Store.Users(serverCtx).Update(serverCtx, usr); err != nil {
unknownErrorWithMessage(w, err, s.Logger) unknownErrorWithMessage(w, err, s.Logger)
@ -236,9 +246,10 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) {
return return
} }
defaultOrg, err := s.Store.Organizations(serverCtx).DefaultOrganization(serverCtx) // If users must be explicitly added to the default organization, respond with 403
if err != nil { // forbidden
unknownErrorWithMessage(w, err, s.Logger) if !defaultOrg.Public {
Error(w, http.StatusForbidden, "This organization is private. To gain access, you must be explicitly added by an administrator.", s.Logger)
return return
} }
@ -252,7 +263,7 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) {
Scheme: scheme, Scheme: scheme,
Roles: []chronograf.Role{ Roles: []chronograf.Role{
{ {
Name: roles.MemberRoleName, Name: defaultOrg.DefaultRole,
// This is the ID of the default organization // This is the ID of the default organization
Organization: fmt.Sprintf("%d", defaultOrg.ID), Organization: fmt.Sprintf("%d", defaultOrg.ID),
}, },
@ -327,11 +338,9 @@ func (s *Service) usersOrganizations(ctx context.Context, u *chronograf.User) ([
return orgs, nil return orgs, nil
} }
// TODO(desa): when https://github.com/influxdata/chronograf/pull/2219 is merge, refactor this to use func hasRoleInDefaultOrganization(u *chronograf.User, orgID string) bool {
// the default organization logic rather than hard coding valies here.
func hasRoleInDefaultOrganization(u *chronograf.User) bool {
for _, role := range u.Roles { for _, role := range u.Roles {
if role.Organization == "0" { if role.Organization == orgID {
return true return true
} }
} }

View File

@ -39,6 +39,71 @@ func TestService_Me(t *testing.T) {
wantContentType string wantContentType string
wantBody string wantBody string
}{ }{
{
name: "Existing user - not member of any organization",
args: args{
w: httptest.NewRecorder(),
r: httptest.NewRequest("GET", "http://example.com/foo", nil),
},
fields: fields{
UseAuth: true,
Logger: log.New(log.DebugLevel),
OrganizationsStore: &mocks.OrganizationsStore{
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
return &chronograf.Organization{
ID: 0,
Name: "Default",
DefaultRole: roles.ViewerRoleName,
Public: false,
}, nil
},
GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) {
switch *q.ID {
case 0:
return &chronograf.Organization{
ID: 0,
Name: "Default",
DefaultRole: roles.ViewerRoleName,
Public: false,
}, nil
case 1:
return &chronograf.Organization{
ID: 1,
Name: "The Bad Place",
Public: false,
}, nil
}
return nil, nil
},
},
UsersStore: &mocks.UsersStore{
AllF: func(ctx context.Context) ([]chronograf.User, error) {
// This function gets to verify that there is at least one first user
return []chronograf.User{{}}, nil
},
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return &chronograf.User{
Name: "me",
Provider: "github",
Scheme: "oauth2",
}, nil
},
UpdateF: func(ctx context.Context, u *chronograf.User) error {
return nil
},
},
},
principal: oauth2.Principal{
Subject: "me",
Issuer: "github",
},
wantStatus: http.StatusForbidden,
wantContentType: "application/json",
wantBody: `{"code":403,"message":"This organization is private. To gain access, you must be explicitly added by an administrator."}`,
},
{ {
name: "Existing user", name: "Existing user",
args: args{ args: args{
@ -53,6 +118,8 @@ func TestService_Me(t *testing.T) {
return &chronograf.Organization{ return &chronograf.Organization{
ID: 0, ID: 0,
Name: "Default", Name: "Default",
DefaultRole: roles.ViewerRoleName,
Public: true,
}, nil }, nil
}, },
GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) {
@ -61,11 +128,14 @@ func TestService_Me(t *testing.T) {
return &chronograf.Organization{ return &chronograf.Organization{
ID: 0, ID: 0,
Name: "Default", Name: "Default",
DefaultRole: roles.ViewerRoleName,
Public: true,
}, nil }, nil
case 1: case 1:
return &chronograf.Organization{ return &chronograf.Organization{
ID: 1, ID: 1,
Name: "The Bad Place", Name: "The Bad Place",
Public: true,
}, nil }, nil
} }
return nil, nil return nil, nil
@ -97,11 +167,11 @@ func TestService_Me(t *testing.T) {
}, },
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantContentType: "application/json", wantContentType: "application/json",
wantBody: `{"name":"me","roles":[{"name":"member","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default"}],"currentOrganization":{"id":"0","name":"Default"}} wantBody: `{"name":"me","roles":[{"name":"viewer","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","public":true,"defaultRole":"viewer"}],"currentOrganization":{"id":"0","defaultRole":"viewer","name":"Default","public":true}}
`, `,
}, },
{ {
name: "New user", name: "new user - default org is public",
args: args{ args: args{
w: httptest.NewRecorder(), w: httptest.NewRecorder(),
r: httptest.NewRequest("GET", "http://example.com/foo", nil), r: httptest.NewRequest("GET", "http://example.com/foo", nil),
@ -113,12 +183,17 @@ func TestService_Me(t *testing.T) {
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
return &chronograf.Organization{ return &chronograf.Organization{
ID: 0, ID: 0,
Name: "The Gnarly Default",
DefaultRole: roles.ViewerRoleName,
Public: true,
}, nil }, nil
}, },
GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) {
return &chronograf.Organization{ return &chronograf.Organization{
ID: 0, ID: 0,
Name: "The Bad Place", Name: "The Gnarly Default",
DefaultRole: roles.ViewerRoleName,
Public: true,
}, nil }, nil
}, },
}, },
@ -147,7 +222,7 @@ func TestService_Me(t *testing.T) {
}, },
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantContentType: "application/json", wantContentType: "application/json",
wantBody: `{"name":"secret","roles":[{"name":"member","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Bad Place"}],"currentOrganization":{"id":"0","name":"The Bad Place"}} wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","public":true,"defaultRole":"viewer"}],"currentOrganization":{"id":"0","name":"The Gnarly Default","public":true,"defaultRole":"viewer"}}
`, `,
}, },
{ {
@ -162,12 +237,14 @@ func TestService_Me(t *testing.T) {
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
return &chronograf.Organization{ return &chronograf.Organization{
ID: 0, ID: 0,
Public: true,
}, nil }, nil
}, },
GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) {
return &chronograf.Organization{ return &chronograf.Organization{
ID: 0, ID: 0,
Name: "The Bad Place", Name: "The Bad Place",
Public: true,
}, nil }, nil
}, },
}, },
@ -227,6 +304,52 @@ func TestService_Me(t *testing.T) {
Issuer: "", Issuer: "",
}, },
}, },
{
name: "new user - default org is private",
args: args{
w: httptest.NewRecorder(),
r: httptest.NewRequest("GET", "http://example.com/foo", nil),
},
fields: fields{
UseAuth: true,
Logger: log.New(log.DebugLevel),
OrganizationsStore: &mocks.OrganizationsStore{
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
return &chronograf.Organization{
ID: 0,
Name: "The Bad Place",
DefaultRole: roles.MemberRoleName,
Public: false,
}, nil
},
},
UsersStore: &mocks.UsersStore{
AllF: func(ctx context.Context) ([]chronograf.User, error) {
// This function gets to verify that there is at least one first user
return []chronograf.User{{}}, nil
},
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return nil, chronograf.ErrUserNotFound
},
AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) {
return u, nil
},
UpdateF: func(ctx context.Context, u *chronograf.User) error {
return nil
},
},
},
principal: oauth2.Principal{
Subject: "secret",
Issuer: "auth0",
},
wantStatus: http.StatusForbidden,
wantContentType: "application/json",
wantBody: `{"code":403,"message":"This organization is private. To gain access, you must be explicitly added by an administrator."}`,
},
} }
for _, tt := range tests { for _, tt := range tests {
tt.args.r = tt.args.r.WithContext(context.WithValue(context.Background(), oauth2.PrincipalKey, tt.principal)) tt.args.r = tt.args.r.WithContext(context.WithValue(context.Background(), oauth2.PrincipalKey, tt.principal))
@ -260,7 +383,7 @@ func TestService_Me(t *testing.T) {
} }
} }
func TestService_MeOrganizations(t *testing.T) { func TestService_UpdateMe(t *testing.T) {
type fields struct { type fields struct {
UsersStore chronograf.UsersStore UsersStore chronograf.UsersStore
OrganizationsStore chronograf.OrganizationsStore OrganizationsStore chronograf.OrganizationsStore
@ -270,7 +393,7 @@ func TestService_MeOrganizations(t *testing.T) {
type args struct { type args struct {
w *httptest.ResponseRecorder w *httptest.ResponseRecorder
r *http.Request r *http.Request
orgRequest *meOrganizationRequest meRequest *meRequest
auth mocks.Authenticator auth mocks.Authenticator
} }
tests := []struct { tests := []struct {
@ -287,7 +410,7 @@ func TestService_MeOrganizations(t *testing.T) {
args: args{ args: args{
w: httptest.NewRecorder(), w: httptest.NewRecorder(),
r: httptest.NewRequest("GET", "http://example.com/foo", nil), r: httptest.NewRequest("GET", "http://example.com/foo", nil),
orgRequest: &meOrganizationRequest{ meRequest: &meRequest{
Organization: "1337", Organization: "1337",
}, },
auth: mocks.Authenticator{}, auth: mocks.Authenticator{},
@ -321,6 +444,8 @@ func TestService_MeOrganizations(t *testing.T) {
return &chronograf.Organization{ return &chronograf.Organization{
ID: 0, ID: 0,
Name: "Default", Name: "Default",
DefaultRole: roles.AdminRoleName,
Public: true,
}, nil }, nil
}, },
GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) {
@ -332,11 +457,14 @@ func TestService_MeOrganizations(t *testing.T) {
return &chronograf.Organization{ return &chronograf.Organization{
ID: 0, ID: 0,
Name: "Default", Name: "Default",
DefaultRole: roles.AdminRoleName,
Public: true,
}, nil }, nil
case 1337: case 1337:
return &chronograf.Organization{ return &chronograf.Organization{
ID: 1337, ID: 1337,
Name: "The ShillBillThrilliettas", Name: "The ShillBillThrilliettas",
Public: true,
}, nil }, nil
} }
return nil, nil return nil, nil
@ -349,14 +477,14 @@ func TestService_MeOrganizations(t *testing.T) {
}, },
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantContentType: "application/json", wantContentType: "application/json",
wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"member","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default"},{"id":"1337","name":"The ShillBillThrilliettas"}],"currentOrganization":{"id":"1337","name":"The ShillBillThrilliettas"}}`, wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"admin","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","public":true,"defaultRole":"admin"},{"id":"1337","name":"The ShillBillThrilliettas","public":true}],"currentOrganization":{"id":"1337","name":"The ShillBillThrilliettas","public":true}}`,
}, },
{ {
name: "Change the current User's organization", name: "Change the current User's organization",
args: args{ args: args{
w: httptest.NewRecorder(), w: httptest.NewRecorder(),
r: httptest.NewRequest("GET", "http://example.com/foo", nil), r: httptest.NewRequest("GET", "http://example.com/foo", nil),
orgRequest: &meOrganizationRequest{ meRequest: &meRequest{
Organization: "1337", Organization: "1337",
}, },
auth: mocks.Authenticator{}, auth: mocks.Authenticator{},
@ -390,6 +518,8 @@ func TestService_MeOrganizations(t *testing.T) {
return &chronograf.Organization{ return &chronograf.Organization{
ID: 0, ID: 0,
Name: "Default", Name: "Default",
DefaultRole: roles.EditorRoleName,
Public: true,
}, nil }, nil
}, },
GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) {
@ -401,11 +531,14 @@ func TestService_MeOrganizations(t *testing.T) {
return &chronograf.Organization{ return &chronograf.Organization{
ID: 1337, ID: 1337,
Name: "The ThrillShilliettos", Name: "The ThrillShilliettos",
Public: false,
}, nil }, nil
case 0: case 0:
return &chronograf.Organization{ return &chronograf.Organization{
ID: 0, ID: 0,
Name: "Default", Name: "Default",
DefaultRole: roles.EditorRoleName,
Public: true,
}, nil }, nil
} }
return nil, nil return nil, nil
@ -419,14 +552,14 @@ func TestService_MeOrganizations(t *testing.T) {
}, },
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantContentType: "application/json", wantContentType: "application/json",
wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"member","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default"},{"id":"1337","name":"The ThrillShilliettos"}],"currentOrganization":{"id":"1337","name":"The ThrillShilliettos"}}`, wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"editor","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","public":true,"defaultRole":"editor"},{"id":"1337","name":"The ThrillShilliettos","public":false}],"currentOrganization":{"id":"1337","name":"The ThrillShilliettos","public":false}}`,
}, },
{ {
name: "Unable to find requested user in valid organization", name: "Unable to find requested user in valid organization",
args: args{ args: args{
w: httptest.NewRecorder(), w: httptest.NewRecorder(),
r: httptest.NewRequest("GET", "http://example.com/foo", nil), r: httptest.NewRequest("GET", "http://example.com/foo", nil),
orgRequest: &meOrganizationRequest{ meRequest: &meRequest{
Organization: "1337", Organization: "1337",
}, },
auth: mocks.Authenticator{}, auth: mocks.Authenticator{},
@ -468,6 +601,7 @@ func TestService_MeOrganizations(t *testing.T) {
return &chronograf.Organization{ return &chronograf.Organization{
ID: 1337, ID: 1337,
Name: "The ShillBillThrilliettas", Name: "The ShillBillThrilliettas",
Public: true,
}, nil }, nil
}, },
}, },
@ -477,16 +611,16 @@ func TestService_MeOrganizations(t *testing.T) {
Issuer: "github", Issuer: "github",
Organization: "1338", Organization: "1338",
}, },
wantStatus: http.StatusBadRequest, wantStatus: http.StatusForbidden,
wantContentType: "application/json", wantContentType: "application/json",
wantBody: `{"code":400,"message":"user not found"}`, wantBody: `{"code":403,"message":"user not found"}`,
}, },
{ {
name: "Unable to find requested organization", name: "Unable to find requested organization",
args: args{ args: args{
w: httptest.NewRecorder(), w: httptest.NewRecorder(),
r: httptest.NewRequest("GET", "http://example.com/foo", nil), r: httptest.NewRequest("GET", "http://example.com/foo", nil),
orgRequest: &meOrganizationRequest{ meRequest: &meRequest{
Organization: "1337", Organization: "1337",
}, },
auth: mocks.Authenticator{}, auth: mocks.Authenticator{},
@ -547,24 +681,24 @@ func TestService_MeOrganizations(t *testing.T) {
UseAuth: tt.fields.UseAuth, UseAuth: tt.fields.UseAuth,
} }
buf, _ := json.Marshal(tt.args.orgRequest) buf, _ := json.Marshal(tt.args.meRequest)
tt.args.r.Body = ioutil.NopCloser(bytes.NewReader(buf)) tt.args.r.Body = ioutil.NopCloser(bytes.NewReader(buf))
tt.args.auth.Principal = tt.principal tt.args.auth.Principal = tt.principal
s.MeOrganization(&tt.args.auth)(tt.args.w, tt.args.r) s.UpdateMe(&tt.args.auth)(tt.args.w, tt.args.r)
resp := tt.args.w.Result() resp := tt.args.w.Result()
content := resp.Header.Get("Content-Type") content := resp.Header.Get("Content-Type")
body, _ := ioutil.ReadAll(resp.Body) body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != tt.wantStatus { if resp.StatusCode != tt.wantStatus {
t.Errorf("%q. Me() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) t.Errorf("%q. UpdateMe() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus)
} }
if tt.wantContentType != "" && content != tt.wantContentType { if tt.wantContentType != "" && content != tt.wantContentType {
t.Errorf("%q. Me() = %v, want %v", tt.name, content, tt.wantContentType) t.Errorf("%q. UpdateMe() = %v, want %v", tt.name, content, tt.wantContentType)
} }
if eq, err := jsonEqual(tt.wantBody, string(body)); err != nil || !eq { if eq, err := jsonEqual(tt.wantBody, string(body)); err != nil || !eq {
t.Errorf("%q. Me() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) t.Errorf("%q. UpdateMe() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody)
} }
} }
} }

View File

@ -198,7 +198,7 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
router.GET("/chronograf/v1/me", service.Me) router.GET("/chronograf/v1/me", service.Me)
// Set current chronograf organization the user is logged into // Set current chronograf organization the user is logged into
router.PUT("/chronograf/v1/me", service.MeOrganization(opts.Auth)) router.PUT("/chronograf/v1/me", service.UpdateMe(opts.Auth))
// TODO(desa): what to do about admin's being able to set superadmin // TODO(desa): what to do about admin's being able to set superadmin
router.GET("/chronograf/v1/users", EnsureAdmin(service.Users)) router.GET("/chronograf/v1/users", EnsureAdmin(service.Users))

View File

@ -20,6 +20,7 @@ func parseOrganizationID(id string) (uint64, error) {
type organizationRequest struct { type organizationRequest struct {
Name string `json:"name"` Name string `json:"name"`
DefaultRole string `json:"defaultRole"` DefaultRole string `json:"defaultRole"`
Public *bool `json:"public"`
} }
func (r *organizationRequest) ValidCreate() error { func (r *organizationRequest) ValidCreate() error {
@ -31,7 +32,7 @@ func (r *organizationRequest) ValidCreate() error {
} }
func (r *organizationRequest) ValidUpdate() error { func (r *organizationRequest) ValidUpdate() error {
if r.Name == "" && r.DefaultRole == "" { if r.Name == "" && r.DefaultRole == "" && r.Public == nil {
return fmt.Errorf("No fields to update") return fmt.Errorf("No fields to update")
} }
@ -57,16 +58,15 @@ func (r *organizationRequest) ValidDefaultRole() error {
type organizationResponse struct { type organizationResponse struct {
Links selfLinks `json:"links"` Links selfLinks `json:"links"`
ID uint64 `json:"id,string"` chronograf.Organization
Name string `json:"name"`
DefaultRole string `json:"defaultRole,omitempty"`
} }
func newOrganizationResponse(o *chronograf.Organization) *organizationResponse { func newOrganizationResponse(o *chronograf.Organization) *organizationResponse {
if o == nil {
o = &chronograf.Organization{}
}
return &organizationResponse{ return &organizationResponse{
ID: o.ID, Organization: *o,
Name: o.Name,
DefaultRole: o.DefaultRole,
Links: selfLinks{ Links: selfLinks{
Self: fmt.Sprintf("/chronograf/v1/organizations/%d", o.ID), Self: fmt.Sprintf("/chronograf/v1/organizations/%d", o.ID),
}, },
@ -124,6 +124,10 @@ func (s *Service) NewOrganization(w http.ResponseWriter, r *http.Request) {
DefaultRole: req.DefaultRole, DefaultRole: req.DefaultRole,
} }
if req.Public != nil {
org.Public = *req.Public
}
res, err := s.Store.Organizations(ctx).Add(ctx, org) res, err := s.Store.Organizations(ctx).Add(ctx, org)
if err != nil { if err != nil {
Error(w, http.StatusBadRequest, err.Error(), s.Logger) Error(w, http.StatusBadRequest, err.Error(), s.Logger)
@ -219,6 +223,10 @@ func (s *Service) UpdateOrganization(w http.ResponseWriter, r *http.Request) {
org.DefaultRole = req.DefaultRole org.DefaultRole = req.DefaultRole
} }
if req.Public != nil {
org.Public = *req.Public
}
err = s.Store.Organizations(ctx).Update(ctx, org) err = s.Store.Organizations(ctx).Update(ctx, org)
if err != nil { if err != nil {
Error(w, http.StatusBadRequest, err.Error(), s.Logger) Error(w, http.StatusBadRequest, err.Error(), s.Logger)

View File

@ -54,6 +54,7 @@ func TestService_OrganizationID(t *testing.T) {
return &chronograf.Organization{ return &chronograf.Organization{
ID: 1337, ID: 1337,
Name: "The Good Place", Name: "The Good Place",
Public: false,
}, nil }, nil
default: default:
return nil, fmt.Errorf("Organization with ID %s not found", *q.ID) return nil, fmt.Errorf("Organization with ID %s not found", *q.ID)
@ -64,7 +65,7 @@ func TestService_OrganizationID(t *testing.T) {
id: "1337", id: "1337",
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantContentType: "application/json", wantContentType: "application/json",
wantBody: `{"id":"1337","name":"The Good Place","links":{"self":"/chronograf/v1/organizations/1337"}}`, wantBody: `{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The Good Place","public":false}`,
}, },
} }
@ -140,10 +141,12 @@ func TestService_Organizations(t *testing.T) {
chronograf.Organization{ chronograf.Organization{
ID: 1337, ID: 1337,
Name: "The Good Place", Name: "The Good Place",
Public: false,
}, },
chronograf.Organization{ chronograf.Organization{
ID: 100, ID: 100,
Name: "The Bad Place", Name: "The Bad Place",
Public: false,
}, },
}, nil }, nil
}, },
@ -151,7 +154,7 @@ func TestService_Organizations(t *testing.T) {
}, },
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantContentType: "application/json", wantContentType: "application/json",
wantBody: `{"organizations":[{"id":"1337","name":"The Good Place","links":{"self":"/chronograf/v1/organizations/1337"}},{"id":"100","name":"The Bad Place","links":{"self":"/chronograf/v1/organizations/100"}}],"links":{"self":"/chronograf/v1/organizations"}}`, wantBody: `{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The Good Place","public":false},{"links":{"self":"/chronograf/v1/organizations/100"},"id":"100","name":"The Bad Place","public":false}]}`,
}, },
} }
@ -192,6 +195,8 @@ func TestService_UpdateOrganization(t *testing.T) {
w *httptest.ResponseRecorder w *httptest.ResponseRecorder
r *http.Request r *http.Request
org *organizationRequest org *organizationRequest
public bool
setPtr bool
} }
tests := []struct { tests := []struct {
name string name string
@ -225,6 +230,8 @@ func TestService_UpdateOrganization(t *testing.T) {
return &chronograf.Organization{ return &chronograf.Organization{
ID: 1337, ID: 1337,
Name: "The Good Place", Name: "The Good Place",
DefaultRole: roles.ViewerRoleName,
Public: false,
}, nil }, nil
}, },
}, },
@ -232,7 +239,73 @@ func TestService_UpdateOrganization(t *testing.T) {
id: "1337", id: "1337",
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantContentType: "application/json", wantContentType: "application/json",
wantBody: `{"id":"1337","name":"The Bad Place","links":{"self":"/chronograf/v1/organizations/1337"}}`, wantBody: `{"id":"1337","name":"The Bad Place","defaultRole":"viewer","links":{"self":"/chronograf/v1/organizations/1337"},"public":false}`,
},
{
name: "Update Organization public",
args: args{
w: httptest.NewRecorder(),
r: httptest.NewRequest(
"GET",
"http://any.url", // can be any valid URL as we are bypassing mux
nil,
),
org: &organizationRequest{},
public: false,
setPtr: true,
},
fields: fields{
Logger: log.New(log.DebugLevel),
OrganizationsStore: &mocks.OrganizationsStore{
UpdateF: func(ctx context.Context, o *chronograf.Organization) error {
return nil
},
GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) {
return &chronograf.Organization{
ID: 0,
Name: "The Good Place",
DefaultRole: roles.ViewerRoleName,
Public: true,
}, nil
},
},
},
id: "0",
wantStatus: http.StatusOK,
wantContentType: "application/json",
wantBody: `{"id":"0","name":"The Good Place","defaultRole":"viewer","public":false,"links":{"self":"/chronograf/v1/organizations/0"}}`,
},
{
name: "Update Organization - nothing to update",
args: args{
w: httptest.NewRecorder(),
r: httptest.NewRequest(
"GET",
"http://any.url", // can be any valid URL as we are bypassing mux
nil,
),
org: &organizationRequest{},
},
fields: fields{
Logger: log.New(log.DebugLevel),
OrganizationsStore: &mocks.OrganizationsStore{
UpdateF: func(ctx context.Context, o *chronograf.Organization) error {
return nil
},
GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) {
return &chronograf.Organization{
ID: 1337,
Name: "The Good Place",
DefaultRole: roles.ViewerRoleName,
Public: true,
}, nil
},
},
},
id: "1337",
wantStatus: http.StatusUnprocessableEntity,
wantContentType: "application/json",
wantBody: `{"code":422,"message":"No fields to update"}`,
}, },
{ {
name: "Update Organization default role", name: "Update Organization default role",
@ -258,6 +331,7 @@ func TestService_UpdateOrganization(t *testing.T) {
ID: 1337, ID: 1337,
Name: "The Good Place", Name: "The Good Place",
DefaultRole: roles.MemberRoleName, DefaultRole: roles.MemberRoleName,
Public: false,
}, nil }, nil
}, },
}, },
@ -265,7 +339,7 @@ func TestService_UpdateOrganization(t *testing.T) {
id: "1337", id: "1337",
wantStatus: http.StatusOK, wantStatus: http.StatusOK,
wantContentType: "application/json", wantContentType: "application/json",
wantBody: `{"id":"1337","name":"The Good Place","defaultRole":"viewer","links":{"self":"/chronograf/v1/organizations/1337"}}`, wantBody: `{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The Good Place","defaultRole":"viewer","public":false}`,
}, },
{ {
name: "Update Organization - invalid update", name: "Update Organization - invalid update",
@ -341,6 +415,11 @@ func TestService_UpdateOrganization(t *testing.T) {
Value: tt.id, Value: tt.id,
}, },
})) }))
if tt.args.setPtr {
tt.args.org.Public = &tt.args.public
}
buf, _ := json.Marshal(tt.args.org) buf, _ := json.Marshal(tt.args.org)
tt.args.r.Body = ioutil.NopCloser(bytes.NewReader(buf)) tt.args.r.Body = ioutil.NopCloser(bytes.NewReader(buf))
s.UpdateOrganization(tt.args.w, tt.args.r) s.UpdateOrganization(tt.args.w, tt.args.r)
@ -496,13 +575,14 @@ func TestService_NewOrganization(t *testing.T) {
return &chronograf.Organization{ return &chronograf.Organization{
ID: 1337, ID: 1337,
Name: "The Good Place", Name: "The Good Place",
Public: false,
}, nil }, nil
}, },
}, },
}, },
wantStatus: http.StatusCreated, wantStatus: http.StatusCreated,
wantContentType: "application/json", wantContentType: "application/json",
wantBody: `{"id":"1337","name":"The Good Place","links":{"self":"/chronograf/v1/organizations/1337"}}`, wantBody: `{"id":"1337","public":false,"name":"The Good Place","links":{"self":"/chronograf/v1/organizations/1337"}}`,
}, },
{ {
name: "Create Organization - no user on context", name: "Create Organization - no user on context",

View File

@ -5,8 +5,10 @@ import uuid from 'node-uuid'
import OrganizationsTableRow from 'src/admin/components/chronograf/OrganizationsTableRow' import OrganizationsTableRow from 'src/admin/components/chronograf/OrganizationsTableRow'
import OrganizationsTableRowDefault from 'src/admin/components/chronograf/OrganizationsTableRowDefault' import OrganizationsTableRowDefault from 'src/admin/components/chronograf/OrganizationsTableRowDefault'
import OrganizationsTableRowNew from 'src/admin/components/chronograf/OrganizationsTableRowNew' import OrganizationsTableRowNew from 'src/admin/components/chronograf/OrganizationsTableRowNew'
import QuestionMarkTooltip from 'shared/components/QuestionMarkTooltip'
import {DEFAULT_ORG_ID} from 'src/admin/constants/dummyUsers' import {DEFAULT_ORG_ID} from 'src/admin/constants/dummyUsers'
import {PUBLIC_TOOLTIP} from 'src/admin/constants/index'
class OrganizationsTable extends Component { class OrganizationsTable extends Component {
constructor(props) { constructor(props) {
@ -16,6 +18,7 @@ class OrganizationsTable extends Component {
isCreatingOrganization: false, isCreatingOrganization: false,
} }
} }
handleClickCreateOrganization = () => { handleClickCreateOrganization = () => {
this.setState({isCreatingOrganization: true}) this.setState({isCreatingOrganization: true})
} }
@ -36,6 +39,7 @@ class OrganizationsTable extends Component {
onDeleteOrg, onDeleteOrg,
onRenameOrg, onRenameOrg,
onChooseDefaultRole, onChooseDefaultRole,
onTogglePublic,
} = this.props } = this.props
const {isCreatingOrganization} = this.state const {isCreatingOrganization} = this.state
@ -62,6 +66,10 @@ class OrganizationsTable extends Component {
<div className="orgs-table--org-labels"> <div className="orgs-table--org-labels">
<div className="orgs-table--id">ID</div> <div className="orgs-table--id">ID</div>
<div className="orgs-table--name">Name</div> <div className="orgs-table--name">Name</div>
<div className="orgs-table--public">
Public{' '}
<QuestionMarkTooltip tipID="public" tipContent={PUBLIC_TOOLTIP} />
</div>
<div className="orgs-table--default-role">Default Role</div> <div className="orgs-table--default-role">Default Role</div>
<div className="orgs-table--delete" /> <div className="orgs-table--delete" />
</div> </div>
@ -77,6 +85,7 @@ class OrganizationsTable extends Component {
? <OrganizationsTableRowDefault ? <OrganizationsTableRowDefault
key={uuid.v4()} key={uuid.v4()}
organization={org} organization={org}
onTogglePublic={onTogglePublic}
onChooseDefaultRole={onChooseDefaultRole} onChooseDefaultRole={onChooseDefaultRole}
/> />
: <OrganizationsTableRow : <OrganizationsTableRow
@ -105,6 +114,7 @@ OrganizationsTable.propTypes = {
onCreateOrg: func.isRequired, onCreateOrg: func.isRequired,
onDeleteOrg: func.isRequired, onDeleteOrg: func.isRequired,
onRenameOrg: func.isRequired, onRenameOrg: func.isRequired,
onTogglePublic: func.isRequired,
onChooseDefaultRole: func.isRequired, onChooseDefaultRole: func.isRequired,
} }
export default OrganizationsTable export default OrganizationsTable

View File

@ -117,6 +117,7 @@ class OrganizationsTableRow extends Component {
{workingName} {workingName}
<span className="icon pencil" /> <span className="icon pencil" />
</div>} </div>}
<div className="orgs-table--public disabled">&mdash;</div>
<div className={defaultRoleClassName}> <div className={defaultRoleClassName}>
<Dropdown <Dropdown
items={dropdownRolesItems} items={dropdownRolesItems}

View File

@ -1,11 +1,17 @@
import React, {PropTypes, Component} from 'react' import React, {PropTypes, Component} from 'react'
import SlideToggle from 'shared/components/SlideToggle'
import Dropdown from 'shared/components/Dropdown' import Dropdown from 'shared/components/Dropdown'
import {USER_ROLES} from 'src/admin/constants/dummyUsers' import {USER_ROLES} from 'src/admin/constants/dummyUsers'
// This is a non-editable organization row, used currently for DEFAULT_ORG // This is a non-editable organization row, used currently for DEFAULT_ORG
class OrganizationsTableRowDefault extends Component { class OrganizationsTableRowDefault extends Component {
togglePublic = () => {
const {organization, onTogglePublic} = this.props
onTogglePublic(organization)
}
handleChooseDefaultRole = role => { handleChooseDefaultRole = role => {
const {organization, onChooseDefaultRole} = this.props const {organization, onChooseDefaultRole} = this.props
onChooseDefaultRole(organization, role.name) onChooseDefaultRole(organization, role.name)
@ -27,6 +33,13 @@ class OrganizationsTableRowDefault extends Component {
<div className="orgs-table--name-disabled"> <div className="orgs-table--name-disabled">
{organization.name} {organization.name}
</div> </div>
<div className="orgs-table--public">
<SlideToggle
size="xs"
active={organization.public}
onToggle={this.togglePublic}
/>
</div>
<div className="orgs-table--default-role"> <div className="orgs-table--default-role">
<Dropdown <Dropdown
items={dropdownRolesItems} items={dropdownRolesItems}
@ -53,6 +66,7 @@ OrganizationsTableRowDefault.propTypes = {
id: string, id: string,
name: string.isRequired, name: string.isRequired,
}).isRequired, }).isRequired,
onTogglePublic: func.isRequired,
onChooseDefaultRole: func.isRequired, onChooseDefaultRole: func.isRequired,
} }

View File

@ -46,3 +46,6 @@ export const NEW_DEFAULT_DATABASE = {
isNew: true, isNew: true,
retentionPolicies: [NEW_DEFAULT_RP], retentionPolicies: [NEW_DEFAULT_RP],
} }
export const PUBLIC_TOOLTIP =
'If set to <code>false</code>, users cannot<br/>authenticate unless an <strong>Admin</strong> explicitly<br/>adds them to the organization.'

View File

@ -29,6 +29,14 @@ class OrganizationsPage extends Component {
deleteOrganizationAsync(organization) deleteOrganizationAsync(organization)
} }
handleTogglePublic = organization => {
const {actions: {updateOrganizationAsync}} = this.props
updateOrganizationAsync(organization, {
...organization,
public: !organization.public,
})
}
handleChooseDefaultRole = (organization, defaultRole) => { handleChooseDefaultRole = (organization, defaultRole) => {
const {actions: {updateOrganizationAsync}} = this.props const {actions: {updateOrganizationAsync}} = this.props
updateOrganizationAsync(organization, {...organization, defaultRole}) updateOrganizationAsync(organization, {...organization, defaultRole})
@ -43,6 +51,7 @@ class OrganizationsPage extends Component {
onCreateOrg={this.handleCreateOrganization} onCreateOrg={this.handleCreateOrganization}
onDeleteOrg={this.handleDeleteOrganization} onDeleteOrg={this.handleDeleteOrganization}
onRenameOrg={this.handleRenameOrganization} onRenameOrg={this.handleRenameOrganization}
onTogglePublic={this.handleTogglePublic}
onChooseDefaultRole={this.handleChooseDefaultRole} onChooseDefaultRole={this.handleChooseDefaultRole}
/> />
) )

View File

@ -47,9 +47,7 @@ input[type="text"].form-control.orgs-table--input {
background-color: $g2-kevlar; background-color: $g2-kevlar;
color: $g13-mist; color: $g13-mist;
position: relative; position: relative;
transition: transition: color 0.4s ease, background-color 0.4s ease,
color 0.4s ease,
background-color 0.4s ease,
border-color 0.4s ease; border-color 0.4s ease;
> span.icon { > span.icon {
@ -68,7 +66,9 @@ input[type="text"].form-control.orgs-table--input {
border-color: $g5-pepper; border-color: $g5-pepper;
cursor: text; cursor: text;
> span.icon {opacity: 1;} > span.icon {
opacity: 1;
}
} }
} }
.orgs-table--name-disabled { .orgs-table--name-disabled {
@ -78,6 +78,29 @@ input[type="text"].form-control.orgs-table--input {
color: $g9-mountain; color: $g9-mountain;
} }
.orgs-table--public {
height: 30px;
margin-right: 4px;
text-align: center;
width: 104px;
background-color: $g4-onyx;
border-radius: 4px;
line-height: 30px;
position: relative;
> .slide-toggle {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
&.disabled {
color: $g9-mountain;
@include no-user-select();
}
}
.orgs-table--default-role, .orgs-table--default-role,
.orgs-table--default-role-disabled { .orgs-table--default-role-disabled {
width: 130px; width: 130px;
@ -102,7 +125,6 @@ input[type="text"].form-control.orgs-table--input {
width: 30px; width: 30px;
} }
/* Table Headers */ /* Table Headers */
.orgs-table--org-labels { .orgs-table--org-labels {
display: flex; display: flex;
@ -113,7 +135,8 @@ input[type="text"].form-control.orgs-table--input {
@include no-user-select(); @include no-user-select();
> .orgs-table--name, > .orgs-table--name,
> .orgs-table--name:hover { > .orgs-table--name:hover,
> .orgs-table--public {
transition: none; transition: none;
background-color: transparent; background-color: transparent;
border-color: transparent; border-color: transparent;
@ -122,11 +145,13 @@ input[type="text"].form-control.orgs-table--input {
> .orgs-table--id, > .orgs-table--id,
> .orgs-table--name, > .orgs-table--name,
> .orgs-table--name:hover, > .orgs-table--name:hover,
> .orgs-table--default-role { > .orgs-table--default-role,
> .orgs-table--public {
color: $g17-whisper; color: $g17-whisper;
font-weight: 500; font-weight: 500;
} }
> .orgs-table--default-role { > .orgs-table--default-role,
> .orgs-table--public {
line-height: 30px; line-height: 30px;
font-size: 13px; font-size: 13px;
padding: 0 11px; padding: 0 11px;