Merge pull request #2442 from influxdata/multitenancy_first_user_superadmin
Make first user superadmin when using `superadmin-first-user-only` CLI flagpull/10616/head
commit
6218d5b56b
|
@ -51,6 +51,24 @@ func (s *UsersStore) each(ctx context.Context, fn func(*chronograf.User)) error
|
|||
})
|
||||
}
|
||||
|
||||
// Num returns the number of users in the UsersStore
|
||||
func (s *UsersStore) Num(ctx context.Context) (int, error) {
|
||||
count := 0
|
||||
|
||||
err := s.client.db.View(func(tx *bolt.Tx) error {
|
||||
return tx.Bucket(UsersBucket).ForEach(func(k, v []byte) error {
|
||||
count++
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// Get searches the UsersStore for user with name
|
||||
func (s *UsersStore) Get(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
|
||||
if q.ID != nil {
|
||||
|
|
|
@ -522,3 +522,67 @@ func TestUsersStore_All(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsersStore_Num(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
users []chronograf.User
|
||||
want int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "No users",
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "Update new user",
|
||||
want: 2,
|
||||
users: []chronograf.User{
|
||||
{
|
||||
Name: "howdy",
|
||||
Provider: "github",
|
||||
Scheme: "oauth2",
|
||||
Roles: []chronograf.Role{
|
||||
{
|
||||
Name: "viewer",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "doody",
|
||||
Provider: "github",
|
||||
Scheme: "oauth2",
|
||||
Roles: []chronograf.Role{
|
||||
{
|
||||
Name: "editor",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
client, err := NewTestClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := client.Open(context.TODO()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
s := client.UsersStore
|
||||
|
||||
for _, u := range tt.users {
|
||||
s.Add(tt.ctx, &u)
|
||||
}
|
||||
got, err := s.Num(tt.ctx)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("%q. UsersStore.Num() error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
||||
continue
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("%q. UsersStore.Num() = %d. want %d", tt.name, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -662,6 +662,8 @@ type UsersStore interface {
|
|||
Get(ctx context.Context, q UserQuery) (*User, error)
|
||||
// Update the user's permissions or roles
|
||||
Update(context.Context, *User) error
|
||||
// Num returns the number of users in the UsersStore
|
||||
Num(context.Context) (int, error)
|
||||
}
|
||||
|
||||
// Database represents a database in a time series source
|
||||
|
|
|
@ -37,6 +37,16 @@ func (c *UserStore) Delete(ctx context.Context, u *chronograf.User) error {
|
|||
return c.Ctrl.DeleteUser(ctx, u.Name)
|
||||
}
|
||||
|
||||
// Number of users in Influx
|
||||
func (c *UserStore) Num(ctx context.Context) (int, error) {
|
||||
all, err := c.All(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return len(all), nil
|
||||
}
|
||||
|
||||
// Get retrieves a user if name exists.
|
||||
func (c *UserStore) Get(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
|
||||
if q.Name == nil {
|
||||
|
|
|
@ -534,6 +534,94 @@ func TestClient_Update(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClient_Num(t *testing.T) {
|
||||
type fields struct {
|
||||
Ctrl *mockCtrl
|
||||
Logger chronograf.Logger
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want []chronograf.User
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Successful Get User",
|
||||
fields: fields{
|
||||
Ctrl: &mockCtrl{
|
||||
users: func(ctx context.Context, name *string) (*enterprise.Users, error) {
|
||||
return &enterprise.Users{
|
||||
Users: []enterprise.User{
|
||||
{
|
||||
Name: "marty",
|
||||
Password: "johnny be good",
|
||||
Permissions: map[string][]string{
|
||||
"": {
|
||||
"ViewChronograf",
|
||||
"ReadData",
|
||||
"WriteData",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
userRoles: func(ctx context.Context) (map[string]enterprise.Roles, error) {
|
||||
return map[string]enterprise.Roles{}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
},
|
||||
want: []chronograf.User{
|
||||
{
|
||||
Name: "marty",
|
||||
Permissions: chronograf.Permissions{
|
||||
{
|
||||
Scope: chronograf.AllScope,
|
||||
Allowed: chronograf.Allowances{"ViewChronograf", "ReadData", "WriteData"},
|
||||
},
|
||||
},
|
||||
Roles: []chronograf.Role{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Failure to get User",
|
||||
fields: fields{
|
||||
Ctrl: &mockCtrl{
|
||||
users: func(ctx context.Context, name *string) (*enterprise.Users, error) {
|
||||
return nil, fmt.Errorf("1.21 Gigawatts! Tom, how could I have been so careless?")
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
c := &enterprise.UserStore{
|
||||
Ctrl: tt.fields.Ctrl,
|
||||
Logger: tt.fields.Logger,
|
||||
}
|
||||
got, err := c.Num(tt.args.ctx)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("%q. Client.Num() error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
||||
continue
|
||||
}
|
||||
if got != len(tt.want) {
|
||||
t.Errorf("%q. Client.Num() = %v, want %v", tt.name, got, len(tt.want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_All(t *testing.T) {
|
||||
type fields struct {
|
||||
Ctrl *mockCtrl
|
||||
|
|
|
@ -126,6 +126,16 @@ func (c *Client) All(ctx context.Context) ([]chronograf.User, error) {
|
|||
return users, nil
|
||||
}
|
||||
|
||||
// Number of users in Influx
|
||||
func (c *Client) Num(ctx context.Context) (int, error) {
|
||||
all, err := c.All(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return len(all), nil
|
||||
}
|
||||
|
||||
// showUsers runs SHOW USERS InfluxQL command and returns chronograf users.
|
||||
func (c *Client) showUsers(ctx context.Context) ([]chronograf.User, error) {
|
||||
res, err := c.Query(ctx, chronograf.Query{
|
||||
|
|
|
@ -573,6 +573,102 @@ func TestClient_revokePermission(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClient_Num(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
statusUsers int
|
||||
showUsers []byte
|
||||
statusGrants int
|
||||
showGrants []byte
|
||||
want []chronograf.User
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "All Users",
|
||||
statusUsers: http.StatusOK,
|
||||
showUsers: []byte(`{"results":[{"series":[{"columns":["user","admin"],"values":[["admin",true],["docbrown",true],["reader",false]]}]}]}`),
|
||||
statusGrants: http.StatusOK,
|
||||
showGrants: []byte(`{"results":[{"series":[{"columns":["database","privilege"],"values":[["mydb","ALL PRIVILEGES"]]}]}]}`),
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
},
|
||||
want: []chronograf.User{
|
||||
{
|
||||
Name: "admin",
|
||||
Permissions: chronograf.Permissions{
|
||||
chronograf.Permission{
|
||||
Scope: "all",
|
||||
Allowed: []string{"ALL"},
|
||||
},
|
||||
chronograf.Permission{
|
||||
Scope: "database",
|
||||
Name: "mydb",
|
||||
Allowed: []string{"WRITE", "READ"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "docbrown",
|
||||
Permissions: chronograf.Permissions{
|
||||
chronograf.Permission{
|
||||
Scope: "all",
|
||||
Allowed: []string{"ALL"},
|
||||
},
|
||||
chronograf.Permission{
|
||||
Scope: "database",
|
||||
Name: "mydb",
|
||||
Allowed: []string{"WRITE", "READ"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "reader",
|
||||
Permissions: chronograf.Permissions{
|
||||
chronograf.Permission{
|
||||
Scope: "database",
|
||||
Name: "mydb",
|
||||
Allowed: []string{"WRITE", "READ"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if path := r.URL.Path; path != "/query" {
|
||||
t.Error("Expected the path to contain `/query` but was", path)
|
||||
}
|
||||
query := r.URL.Query().Get("q")
|
||||
if strings.Contains(query, "GRANTS") {
|
||||
rw.WriteHeader(tt.statusGrants)
|
||||
rw.Write(tt.showGrants)
|
||||
} else if strings.Contains(query, "USERS") {
|
||||
rw.WriteHeader(tt.statusUsers)
|
||||
rw.Write(tt.showUsers)
|
||||
}
|
||||
}))
|
||||
u, _ := url.Parse(ts.URL)
|
||||
c := &Client{
|
||||
URL: u,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
}
|
||||
defer ts.Close()
|
||||
got, err := c.Num(tt.args.ctx)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("%q. Client.Num() error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
||||
continue
|
||||
}
|
||||
if got != len(tt.want) {
|
||||
t.Errorf("%q. Client.Num() = %v, want %v", tt.name, got, len(tt.want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_All(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
|
|
|
@ -15,6 +15,7 @@ type UsersStore struct {
|
|||
DeleteF func(context.Context, *chronograf.User) error
|
||||
GetF func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error)
|
||||
UpdateF func(context.Context, *chronograf.User) error
|
||||
NumF func(context.Context) (int, error)
|
||||
}
|
||||
|
||||
// All lists all users from the UsersStore
|
||||
|
@ -22,6 +23,11 @@ func (s *UsersStore) All(ctx context.Context) ([]chronograf.User, error) {
|
|||
return s.AllF(ctx)
|
||||
}
|
||||
|
||||
// Num returns the number of users in the UsersStore
|
||||
func (s *UsersStore) Num(ctx context.Context) (int, error) {
|
||||
return s.NumF(ctx)
|
||||
}
|
||||
|
||||
// Add a new User in the UsersStore
|
||||
func (s *UsersStore) Add(ctx context.Context, u *chronograf.User) (*chronograf.User, error) {
|
||||
return s.AddF(ctx, u)
|
||||
|
|
|
@ -31,3 +31,7 @@ func (s *UsersStore) Get(ctx context.Context, q chronograf.UserQuery) (*chronogr
|
|||
func (s *UsersStore) Update(context.Context, *chronograf.User) error {
|
||||
return fmt.Errorf("failed to update user")
|
||||
}
|
||||
|
||||
func (s *UsersStore) Num(context.Context) (int, error) {
|
||||
return 0, fmt.Errorf("failed to get number of users")
|
||||
}
|
||||
|
|
|
@ -241,3 +241,20 @@ func (s *UsersStore) All(ctx context.Context) ([]chronograf.User, error) {
|
|||
|
||||
return us, nil
|
||||
}
|
||||
|
||||
// Num returns the number of users in the UsersStore
|
||||
// This is unperformant, but should rarely be used.
|
||||
func (s *UsersStore) Num(ctx context.Context) (int, error) {
|
||||
err := validOrganization(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// retrieve all users from the underlying UsersStore
|
||||
usrs, err := s.All(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return len(usrs), nil
|
||||
}
|
||||
|
|
|
@ -875,3 +875,131 @@ func TestUsersStore_All(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsersStore_Num(t *testing.T) {
|
||||
type fields struct {
|
||||
UsersStore chronograf.UsersStore
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
ctx context.Context
|
||||
orgID string
|
||||
want int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "No users",
|
||||
fields: fields{
|
||||
UsersStore: &mocks.UsersStore{
|
||||
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||
return []chronograf.User{
|
||||
{
|
||||
Name: "howdy",
|
||||
Provider: "github",
|
||||
Scheme: "oauth2",
|
||||
Roles: []chronograf.Role{
|
||||
{
|
||||
Organization: "1338",
|
||||
Name: "viewer",
|
||||
},
|
||||
{
|
||||
Organization: "1336",
|
||||
Name: "viewer",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "doody2",
|
||||
Provider: "github",
|
||||
Scheme: "oauth2",
|
||||
Roles: []chronograf.Role{
|
||||
{
|
||||
Organization: "1337",
|
||||
Name: "editor",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "doody",
|
||||
Provider: "github",
|
||||
Scheme: "oauth2",
|
||||
Roles: []chronograf.Role{
|
||||
{
|
||||
Organization: "1338",
|
||||
Name: "editor",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
ctx: context.Background(),
|
||||
orgID: "2330",
|
||||
},
|
||||
{
|
||||
name: "get all users",
|
||||
orgID: "1338",
|
||||
fields: fields{
|
||||
UsersStore: &mocks.UsersStore{
|
||||
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||
return []chronograf.User{
|
||||
{
|
||||
Name: "howdy",
|
||||
Provider: "github",
|
||||
Scheme: "oauth2",
|
||||
Roles: []chronograf.Role{
|
||||
{
|
||||
Organization: "1338",
|
||||
Name: "viewer",
|
||||
},
|
||||
{
|
||||
Organization: "1336",
|
||||
Name: "viewer",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "doody2",
|
||||
Provider: "github",
|
||||
Scheme: "oauth2",
|
||||
Roles: []chronograf.Role{
|
||||
{
|
||||
Organization: "1337",
|
||||
Name: "editor",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "doody",
|
||||
Provider: "github",
|
||||
Scheme: "oauth2",
|
||||
Roles: []chronograf.Role{
|
||||
{
|
||||
Organization: "1338",
|
||||
Name: "editor",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
ctx: context.Background(),
|
||||
want: 2,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt.ctx = context.WithValue(tt.ctx, organizations.ContextKey, tt.orgID)
|
||||
s := organizations.NewUsersStore(tt.fields.UsersStore, tt.orgID)
|
||||
got, err := s.Num(tt.ctx)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("%q. UsersStore.Num() error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
||||
continue
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("%q. UsersStore.Num() = %d. want %d", tt.name, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -302,15 +302,18 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func (s *Service) firstUser() bool {
|
||||
serverCtx := serverContext(context.Background())
|
||||
users, err := s.Store.Users(serverCtx).All(serverCtx)
|
||||
numUsers, err := s.Store.Users(serverCtx).Num(serverCtx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return len(users) == 0
|
||||
return numUsers == 0
|
||||
}
|
||||
func (s *Service) newUsersAreSuperAdmin() bool {
|
||||
return !s.NewUsersNotSuperAdmin
|
||||
if s.firstUser() {
|
||||
return true
|
||||
}
|
||||
return !s.SuperAdminFirstUserOnly
|
||||
}
|
||||
|
||||
func (s *Service) usersOrganizations(ctx context.Context, u *chronograf.User) ([]chronograf.Organization, error) {
|
||||
|
|
|
@ -21,11 +21,11 @@ type MockUsers struct{}
|
|||
|
||||
func TestService_Me(t *testing.T) {
|
||||
type fields struct {
|
||||
UsersStore chronograf.UsersStore
|
||||
OrganizationsStore chronograf.OrganizationsStore
|
||||
Logger chronograf.Logger
|
||||
UseAuth bool
|
||||
NewUsersNotSuperAdmin bool
|
||||
UsersStore chronograf.UsersStore
|
||||
OrganizationsStore chronograf.OrganizationsStore
|
||||
Logger chronograf.Logger
|
||||
UseAuth bool
|
||||
SuperAdminFirstUserOnly bool
|
||||
}
|
||||
type args struct {
|
||||
w *httptest.ResponseRecorder
|
||||
|
@ -47,9 +47,9 @@ func TestService_Me(t *testing.T) {
|
|||
r: httptest.NewRequest("GET", "http://example.com/foo", nil),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
NewUsersNotSuperAdmin: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
UseAuth: true,
|
||||
SuperAdminFirstUserOnly: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
OrganizationsStore: &mocks.OrganizationsStore{
|
||||
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
|
||||
return &chronograf.Organization{
|
||||
|
@ -79,9 +79,9 @@ func TestService_Me(t *testing.T) {
|
|||
},
|
||||
},
|
||||
UsersStore: &mocks.UsersStore{
|
||||
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||
NumF: func(ctx context.Context) (int, error) {
|
||||
// This function gets to verify that there is at least one first user
|
||||
return []chronograf.User{{}}, nil
|
||||
return 1, nil
|
||||
},
|
||||
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
|
||||
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
|
||||
|
@ -144,6 +144,10 @@ func TestService_Me(t *testing.T) {
|
|||
},
|
||||
},
|
||||
UsersStore: &mocks.UsersStore{
|
||||
NumF: func(ctx context.Context) (int, error) {
|
||||
// This function gets to verify that there is at least one first user
|
||||
return 1, 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")
|
||||
|
@ -175,9 +179,9 @@ func TestService_Me(t *testing.T) {
|
|||
r: httptest.NewRequest("GET", "http://example.com/foo", nil),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
NewUsersNotSuperAdmin: false,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
UseAuth: true,
|
||||
SuperAdminFirstUserOnly: false,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
OrganizationsStore: &mocks.OrganizationsStore{
|
||||
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
|
||||
return &chronograf.Organization{
|
||||
|
@ -197,6 +201,10 @@ func TestService_Me(t *testing.T) {
|
|||
},
|
||||
},
|
||||
UsersStore: &mocks.UsersStore{
|
||||
NumF: func(ctx context.Context) (int, error) {
|
||||
// This function gets to verify that there is at least one first user
|
||||
return 1, 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")
|
||||
|
@ -221,15 +229,15 @@ func TestService_Me(t *testing.T) {
|
|||
`,
|
||||
},
|
||||
{
|
||||
name: "New user - New users not super admin",
|
||||
name: "New user - New users not super admin, not first user",
|
||||
args: args{
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest("GET", "http://example.com/foo", nil),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
NewUsersNotSuperAdmin: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
UseAuth: true,
|
||||
SuperAdminFirstUserOnly: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
OrganizationsStore: &mocks.OrganizationsStore{
|
||||
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
|
||||
return &chronograf.Organization{
|
||||
|
@ -249,6 +257,10 @@ func TestService_Me(t *testing.T) {
|
|||
},
|
||||
},
|
||||
UsersStore: &mocks.UsersStore{
|
||||
NumF: func(ctx context.Context) (int, error) {
|
||||
// This function gets to verify that there is at least one first user
|
||||
return 1, 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")
|
||||
|
@ -270,6 +282,62 @@ func TestService_Me(t *testing.T) {
|
|||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
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"}}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "New user - New users not super admin, first user",
|
||||
args: args{
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest("GET", "http://example.com/foo", nil),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
SuperAdminFirstUserOnly: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
OrganizationsStore: &mocks.OrganizationsStore{
|
||||
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
|
||||
return &chronograf.Organization{
|
||||
ID: 0,
|
||||
Name: "The Gnarly Default",
|
||||
DefaultRole: roles.ViewerRoleName,
|
||||
Public: true,
|
||||
}, nil
|
||||
},
|
||||
GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) {
|
||||
return &chronograf.Organization{
|
||||
ID: 0,
|
||||
Name: "The Gnarly Default",
|
||||
DefaultRole: roles.ViewerRoleName,
|
||||
Public: true,
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
UsersStore: &mocks.UsersStore{
|
||||
NumF: func(ctx context.Context) (int, error) {
|
||||
// This function gets to verify that there is at least one first user
|
||||
return 0, 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.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"name":"secret","superAdmin":true,"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"}}
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -279,8 +347,8 @@ func TestService_Me(t *testing.T) {
|
|||
r: httptest.NewRequest("GET", "http://example.com/foo", nil),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
NewUsersNotSuperAdmin: true,
|
||||
UseAuth: true,
|
||||
SuperAdminFirstUserOnly: true,
|
||||
OrganizationsStore: &mocks.OrganizationsStore{
|
||||
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
|
||||
return &chronograf.Organization{
|
||||
|
@ -297,6 +365,10 @@ func TestService_Me(t *testing.T) {
|
|||
},
|
||||
},
|
||||
UsersStore: &mocks.UsersStore{
|
||||
NumF: func(ctx context.Context) (int, error) {
|
||||
// This function gets to verify that there is at least one first user
|
||||
return 1, nil
|
||||
},
|
||||
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
|
||||
return nil, chronograf.ErrUserNotFound
|
||||
},
|
||||
|
@ -324,9 +396,9 @@ func TestService_Me(t *testing.T) {
|
|||
r: httptest.NewRequest("GET", "http://example.com/foo", nil),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: false,
|
||||
NewUsersNotSuperAdmin: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
UseAuth: false,
|
||||
SuperAdminFirstUserOnly: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
|
@ -340,9 +412,9 @@ func TestService_Me(t *testing.T) {
|
|||
r: httptest.NewRequest("GET", "http://example.com/foo", nil),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
NewUsersNotSuperAdmin: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
UseAuth: true,
|
||||
SuperAdminFirstUserOnly: true,
|
||||
Logger: log.New(log.DebugLevel),
|
||||
},
|
||||
wantStatus: http.StatusUnprocessableEntity,
|
||||
principal: oauth2.Principal{
|
||||
|
@ -370,9 +442,9 @@ func TestService_Me(t *testing.T) {
|
|||
},
|
||||
},
|
||||
UsersStore: &mocks.UsersStore{
|
||||
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||
NumF: func(ctx context.Context) (int, error) {
|
||||
// This function gets to verify that there is at least one first user
|
||||
return []chronograf.User{{}}, nil
|
||||
return 1, nil
|
||||
},
|
||||
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
|
||||
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
|
||||
|
@ -404,9 +476,9 @@ func TestService_Me(t *testing.T) {
|
|||
UsersStore: tt.fields.UsersStore,
|
||||
OrganizationsStore: tt.fields.OrganizationsStore,
|
||||
},
|
||||
Logger: tt.fields.Logger,
|
||||
UseAuth: tt.fields.UseAuth,
|
||||
NewUsersNotSuperAdmin: tt.fields.NewUsersNotSuperAdmin,
|
||||
Logger: tt.fields.Logger,
|
||||
UseAuth: tt.fields.UseAuth,
|
||||
SuperAdminFirstUserOnly: tt.fields.SuperAdminFirstUserOnly,
|
||||
}
|
||||
|
||||
s.Me(tt.args.w, tt.args.r)
|
||||
|
|
|
@ -52,13 +52,12 @@ type Server struct {
|
|||
|
||||
NewSources string `long:"new-sources" description:"Config for adding a new InfluxDB source and Kapacitor server, in JSON as an array of objects, and surrounded by single quotes. E.g. --new-sources='[{\"influxdb\":{\"name\":\"Influx 1\",\"username\":\"user1\",\"password\":\"pass1\",\"url\":\"http://localhost:8086\",\"metaUrl\":\"http://metaurl.com\",\"type\":\"influx-enterprise\",\"insecureSkipVerify\":false,\"default\":true,\"telegraf\":\"telegraf\",\"sharedSecret\":\"cubeapples\"},\"kapacitor\":{\"name\":\"Kapa 1\",\"url\":\"http://localhost:9092\",\"active\":true}}]'" env:"NEW_SOURCES" hidden:"true"`
|
||||
|
||||
Develop bool `short:"d" long:"develop" description:"Run server in develop mode."`
|
||||
BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (e.g. './chronograf-v1.db')" env:"BOLT_PATH" default:"chronograf-v1.db"`
|
||||
CannedPath string `short:"c" long:"canned-path" description:"Path to directory of pre-canned application layouts (/usr/share/chronograf/canned)" env:"CANNED_PATH" default:"canned"`
|
||||
TokenSecret string `short:"t" long:"token-secret" description:"Secret to sign tokens" env:"TOKEN_SECRET"`
|
||||
AuthDuration time.Duration `long:"auth-duration" default:"720h" description:"Total duration of cookie life for authentication (in hours). 0 means authentication expires on browser close." env:"AUTH_DURATION"`
|
||||
// TODO(desa): think of a better name
|
||||
NewUsersNotSuperAdmin bool `long:"new-users-not-superadmin" description:"All new users will not be given the SuperAdmin status" env:"NEW_USERS_NOT_SUPERADMIN"`
|
||||
Develop bool `short:"d" long:"develop" description:"Run server in develop mode."`
|
||||
BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (e.g. './chronograf-v1.db')" env:"BOLT_PATH" default:"chronograf-v1.db"`
|
||||
CannedPath string `short:"c" long:"canned-path" description:"Path to directory of pre-canned application layouts (/usr/share/chronograf/canned)" env:"CANNED_PATH" default:"canned"`
|
||||
TokenSecret string `short:"t" long:"token-secret" description:"Secret to sign tokens" env:"TOKEN_SECRET"`
|
||||
AuthDuration time.Duration `long:"auth-duration" default:"720h" description:"Total duration of cookie life for authentication (in hours). 0 means authentication expires on browser close." env:"AUTH_DURATION"`
|
||||
SuperAdminFirstUserOnly bool `long:"superadmin-first-user-only" description:"All new users will not be given the SuperAdmin status" env:"SUPERADMIN_FIRST_USER_ONLY"`
|
||||
|
||||
GithubClientID string `short:"i" long:"github-client-id" description:"Github Client ID for OAuth 2 support" env:"GH_CLIENT_ID"`
|
||||
GithubClientSecret string `short:"s" long:"github-client-secret" description:"Github Client Secret for OAuth 2 support" env:"GH_CLIENT_SECRET"`
|
||||
|
@ -302,8 +301,7 @@ func (s *Server) Serve(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
service := openService(ctx, s.BoltPath, layoutBuilder, sourcesBuilder, kapacitorBuilder, logger, s.useAuth())
|
||||
// TODO(desa): better name
|
||||
service.NewUsersNotSuperAdmin = s.NewUsersNotSuperAdmin
|
||||
service.SuperAdminFirstUserOnly = s.SuperAdminFirstUserOnly
|
||||
if err := service.HandleNewSources(ctx, s.NewSources); err != nil {
|
||||
logger.
|
||||
WithField("component", "server").
|
||||
|
|
|
@ -11,13 +11,12 @@ import (
|
|||
|
||||
// Service handles REST calls to the persistence
|
||||
type Service struct {
|
||||
Store DataStore
|
||||
TimeSeriesClient TimeSeriesClient
|
||||
Logger chronograf.Logger
|
||||
UseAuth bool
|
||||
// TODO(desa): better name
|
||||
NewUsersNotSuperAdmin bool
|
||||
Databases chronograf.Databases
|
||||
Store DataStore
|
||||
TimeSeriesClient TimeSeriesClient
|
||||
Logger chronograf.Logger
|
||||
UseAuth bool
|
||||
SuperAdminFirstUserOnly bool
|
||||
Databases chronograf.Databases
|
||||
}
|
||||
|
||||
// TimeSeriesClient returns the correct client for a time series database.
|
||||
|
|
Loading…
Reference in New Issue