Add roles to chronograf
parent
5eed15b450
commit
08271f25ef
|
@ -56,6 +56,29 @@ type TimeSeries interface {
|
|||
Users(context.Context) UsersStore
|
||||
// Allowances returns all valid names permissions in this database
|
||||
Allowances(context.Context) Allowances
|
||||
// Roles represents the roles associated with this TimesSeriesDatabase
|
||||
Roles(context.Context) (RolesStore, error)
|
||||
}
|
||||
|
||||
// Role is a restricted set of permissions assigned to a set of users.
|
||||
type Role struct {
|
||||
Name string `json:"name"`
|
||||
Permissions Permissions `json:"permissions,omitempty"`
|
||||
Users []User `json:"users,omitempty"`
|
||||
}
|
||||
|
||||
// RolesStore is the Storage and retrieval of authentication information
|
||||
type RolesStore interface {
|
||||
// All lists all roles from the RolesStore
|
||||
All(context.Context) ([]Role, error)
|
||||
// Create a new Role in the RolesStore
|
||||
Add(context.Context, *Role) (*Role, error)
|
||||
// Delete the Role from the RolesStore
|
||||
Delete(context.Context, *Role) error
|
||||
// Get retrieves a role if name exists.
|
||||
Get(ctx context.Context, name string) (*Role, error)
|
||||
// Update the roles' users or permissions
|
||||
Update(context.Context, *Role) error
|
||||
}
|
||||
|
||||
// Range represents an upper and lower bound for data
|
||||
|
@ -249,7 +272,7 @@ type Scope string
|
|||
|
||||
// User represents an authenticated user.
|
||||
type User struct {
|
||||
Name string `json:"username"`
|
||||
Name string `json:"name"`
|
||||
Passwd string `json:"password"`
|
||||
Permissions Permissions `json:"permissions,omitempty"`
|
||||
}
|
||||
|
|
|
@ -13,21 +13,34 @@ import (
|
|||
|
||||
var _ chronograf.TimeSeries = &Client{}
|
||||
|
||||
// Ctrl represents administrative controls over an Influx Enterprise cluster
|
||||
type Ctrl interface {
|
||||
ShowCluster(ctx context.Context) (*Cluster, error)
|
||||
|
||||
Users(ctx context.Context, name *string) (*Users, error)
|
||||
User(ctx context.Context, name string) (*User, error)
|
||||
CreateUser(ctx context.Context, name, passwd string) error
|
||||
DeleteUser(ctx context.Context, name string) error
|
||||
ChangePassword(ctx context.Context, name, passwd string) error
|
||||
SetUserPerms(ctx context.Context, name string, perms Permissions) error
|
||||
|
||||
Roles(ctx context.Context, name *string) (*Roles, error)
|
||||
Role(ctx context.Context, name string) (*Role, error)
|
||||
CreateRole(ctx context.Context, name string) error
|
||||
DeleteRole(ctx context.Context, name string) error
|
||||
SetRolePerms(ctx context.Context, name string, perms Permissions) error
|
||||
SetRoleUsers(ctx context.Context, name string, users []string) error
|
||||
}
|
||||
|
||||
// Client is a device for retrieving time series data from an Influx Enterprise
|
||||
// cluster. It is configured using the addresses of one or more meta node URLs.
|
||||
// Data node URLs are retrieved automatically from the meta nodes and queries
|
||||
// are appropriately load balanced across the cluster.
|
||||
type Client struct {
|
||||
Ctrl interface {
|
||||
ShowCluster(ctx context.Context) (*Cluster, error)
|
||||
User(ctx context.Context, name string) (*User, error)
|
||||
CreateUser(ctx context.Context, name, passwd string) error
|
||||
DeleteUser(ctx context.Context, name string) error
|
||||
ChangePassword(ctx context.Context, name, passwd string) error
|
||||
Users(ctx context.Context, name *string) (*Users, error)
|
||||
SetUserPerms(ctx context.Context, name string, perms Permissions) error
|
||||
}
|
||||
Logger chronograf.Logger
|
||||
Ctrl
|
||||
UsersStore chronograf.UsersStore
|
||||
RolesStore chronograf.RolesStore
|
||||
Logger chronograf.Logger
|
||||
|
||||
dataNodes *ring.Ring
|
||||
opened bool
|
||||
|
@ -58,8 +71,17 @@ func NewClientWithURL(mu, username, password string, tls bool, lg chronograf.Log
|
|||
return nil, err
|
||||
}
|
||||
metaURL.User = url.UserPassword(username, password)
|
||||
ctrl := NewMetaClient(metaURL)
|
||||
return &Client{
|
||||
Ctrl: NewMetaClient(metaURL),
|
||||
Ctrl: ctrl,
|
||||
UsersStore: &UserStore{
|
||||
Ctrl: ctrl,
|
||||
Logger: lg,
|
||||
},
|
||||
RolesStore: &RolesStore{
|
||||
Ctrl: ctrl,
|
||||
Logger: lg,
|
||||
},
|
||||
Logger: lg,
|
||||
}, nil
|
||||
}
|
||||
|
@ -100,7 +122,12 @@ func (c *Client) Query(ctx context.Context, q chronograf.Query) (chronograf.Resp
|
|||
|
||||
// Users is the interface to the users within Influx Enterprise
|
||||
func (c *Client) Users(context.Context) chronograf.UsersStore {
|
||||
return c
|
||||
return c.UsersStore
|
||||
}
|
||||
|
||||
// Roles provide a grouping of permissions given to a grouping of users
|
||||
func (c *Client) Roles(ctx context.Context) (chronograf.RolesStore, error) {
|
||||
return c.RolesStore, nil
|
||||
}
|
||||
|
||||
// Allowances returns all Influx Enterprise permission strings
|
||||
|
|
|
@ -14,10 +14,14 @@ import (
|
|||
|
||||
func Test_Enterprise_FetchesDataNodes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctrl := &ControlClient{
|
||||
Cluster: &enterprise.Cluster{},
|
||||
showClustersCalled := false
|
||||
ctrl := &mockCtrl{
|
||||
showCluster: func(ctx context.Context) (*enterprise.Cluster, error) {
|
||||
showClustersCalled = true
|
||||
return &enterprise.Cluster{}, nil
|
||||
},
|
||||
}
|
||||
|
||||
cl := &enterprise.Client{
|
||||
Ctrl: ctrl,
|
||||
}
|
||||
|
@ -29,7 +33,7 @@ func Test_Enterprise_FetchesDataNodes(t *testing.T) {
|
|||
t.Fatal("Unexpected error while creating enterprise client. err:", err)
|
||||
}
|
||||
|
||||
if ctrl.ShowClustersCalled != true {
|
||||
if showClustersCalled != true {
|
||||
t.Fatal("Expected request to meta node but none was issued")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,30 @@ func (cc *ControlClient) SetUserPerms(ctx context.Context, name string, perms en
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cc *ControlClient) CreateRole(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cc *ControlClient) Role(ctx context.Context, name string) (*enterprise.Role, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (ccm *ControlClient) Roles(ctx context.Context, name *string) (*enterprise.Roles, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (cc *ControlClient) DeleteRole(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cc *ControlClient) SetRolePerms(ctx context.Context, name string, perms enterprise.Permissions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cc *ControlClient) SetRoleUsers(ctx context.Context, name string, users []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type TimeSeries struct {
|
||||
URLs []string
|
||||
Response Response
|
||||
|
@ -86,6 +110,10 @@ func (ts *TimeSeries) Users(ctx context.Context) chronograf.UsersStore {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (ts *TimeSeries) Roles(ctx context.Context) (chronograf.RolesStore, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (ts *TimeSeries) Allowances(ctx context.Context) chronograf.Allowances {
|
||||
return chronograf.Allowances{}
|
||||
}
|
||||
|
|
|
@ -1,50 +1,105 @@
|
|||
package enterprise
|
||||
|
||||
/*
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
// RolesStore uses a control client operate on Influx Enterprise roles. Roles are
|
||||
// groups of permissions applied to groups of users
|
||||
type RolesStore struct {
|
||||
Ctrl
|
||||
Logger chronograf.Logger
|
||||
}
|
||||
|
||||
// Add creates a new Role in Influx Enterprise
|
||||
func (c *Client) Add(ctx context.Context, u *chronograf.Role) (*chronograf.Role, error) {
|
||||
if err := c.Ctrl.CreateRole(ctx, u.Name, u.Passwd); err != nil {
|
||||
// This must be done in three smaller steps: creating, setting permissions, setting users.
|
||||
func (c *RolesStore) Add(ctx context.Context, u *chronograf.Role) (*chronograf.Role, error) {
|
||||
if err := c.Ctrl.CreateRole(ctx, u.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.Ctrl.SetRolePerms(ctx, u.Name, ToEnterprise(u.Permissions)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users := make([]string, len(u.Users))
|
||||
for i, u := range u.Users {
|
||||
users[i] = u.Name
|
||||
}
|
||||
if err := c.Ctrl.SetRoleUsers(ctx, u.Name, users); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// Delete the Role from Influx Enterprise
|
||||
func (c *Client) Delete(ctx context.Context, u *chronograf.Role) error {
|
||||
func (c *RolesStore) Delete(ctx context.Context, u *chronograf.Role) error {
|
||||
return c.Ctrl.DeleteRole(ctx, u.Name)
|
||||
}
|
||||
|
||||
// Get retrieves a Role if name exists.
|
||||
func (c *Client) Get(ctx context.Context, name string) (*chronograf.Role, error) {
|
||||
u, err := c.Ctrl.Role(ctx, name)
|
||||
func (c *RolesStore) Get(ctx context.Context, name string) (*chronograf.Role, error) {
|
||||
role, err := c.Ctrl.Role(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Hydrate all the users to gather their permissions and their roles.
|
||||
users := make([]chronograf.User, len(role.Users))
|
||||
for i, u := range role.Users {
|
||||
user, err := c.Ctrl.User(ctx, u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
users[i] = chronograf.User{
|
||||
Name: user.Name,
|
||||
Permissions: ToChronograf(user.Permissions),
|
||||
}
|
||||
}
|
||||
return &chronograf.Role{
|
||||
Name: u.Name,
|
||||
Permissions: toChronograf(u.Permissions),
|
||||
Name: role.Name,
|
||||
Permissions: ToChronograf(role.Permissions),
|
||||
Users: users,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Update the Role's permissions or roles
|
||||
func (c *Client) Update(ctx context.Context, u *chronograf.Role) error {
|
||||
perms := toEnterprise(u.Permissions)
|
||||
return c.Ctrl.SetRolePerms(ctx, u.Name, perms)
|
||||
// Update the Role's permissions and roles
|
||||
func (c *RolesStore) Update(ctx context.Context, u *chronograf.Role) error {
|
||||
perms := ToEnterprise(u.Permissions)
|
||||
if err := c.Ctrl.SetRolePerms(ctx, u.Name, perms); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
users := make([]string, len(u.Users))
|
||||
for i, u := range u.Users {
|
||||
users[i] = u.Name
|
||||
}
|
||||
return c.Ctrl.SetRoleUsers(ctx, u.Name, users)
|
||||
}
|
||||
|
||||
// All is all Roles in influx
|
||||
func (c *Client) All(ctx context.Context) ([]chronograf.Role, error) {
|
||||
func (c *RolesStore) All(ctx context.Context) ([]chronograf.Role, error) {
|
||||
all, err := c.Ctrl.Roles(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := make([]chronograf.Role, len(all.Roles))
|
||||
for i, Role := range all.Roles {
|
||||
for i, role := range all.Roles {
|
||||
|
||||
users := make([]chronograf.User, len(role.Users))
|
||||
for i, user := range role.Users {
|
||||
users[i] = chronograf.User{
|
||||
Name: user,
|
||||
}
|
||||
}
|
||||
|
||||
res[i] = chronograf.Role{
|
||||
Name: Role.Name,
|
||||
Permissions: toChronograf(Role.Permissions),
|
||||
Name: role.Name,
|
||||
Permissions: ToChronograf(role.Permissions),
|
||||
Users: users,
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}*/
|
||||
}
|
||||
|
|
|
@ -6,8 +6,14 @@ import (
|
|||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
// UserStore uses a control client operate on Influx Enterprise users
|
||||
type UserStore struct {
|
||||
Ctrl
|
||||
Logger chronograf.Logger
|
||||
}
|
||||
|
||||
// Add creates a new User in Influx Enterprise
|
||||
func (c *Client) Add(ctx context.Context, u *chronograf.User) (*chronograf.User, error) {
|
||||
func (c *UserStore) Add(ctx context.Context, u *chronograf.User) (*chronograf.User, error) {
|
||||
if err := c.Ctrl.CreateUser(ctx, u.Name, u.Passwd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -15,35 +21,35 @@ func (c *Client) Add(ctx context.Context, u *chronograf.User) (*chronograf.User,
|
|||
}
|
||||
|
||||
// Delete the User from Influx Enterprise
|
||||
func (c *Client) Delete(ctx context.Context, u *chronograf.User) error {
|
||||
func (c *UserStore) Delete(ctx context.Context, u *chronograf.User) error {
|
||||
return c.Ctrl.DeleteUser(ctx, u.Name)
|
||||
}
|
||||
|
||||
// Get retrieves a user if name exists.
|
||||
func (c *Client) Get(ctx context.Context, name string) (*chronograf.User, error) {
|
||||
func (c *UserStore) Get(ctx context.Context, name string) (*chronograf.User, error) {
|
||||
u, err := c.Ctrl.User(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &chronograf.User{
|
||||
Name: u.Name,
|
||||
Permissions: toChronograf(u.Permissions),
|
||||
Permissions: ToChronograf(u.Permissions),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Update the user's permissions or roles
|
||||
func (c *Client) Update(ctx context.Context, u *chronograf.User) error {
|
||||
func (c *UserStore) Update(ctx context.Context, u *chronograf.User) error {
|
||||
// Only allow one type of change at a time. If it is a password
|
||||
// change then do it and return without any changes to permissions
|
||||
if u.Passwd != "" {
|
||||
return c.Ctrl.ChangePassword(ctx, u.Name, u.Passwd)
|
||||
}
|
||||
perms := toEnterprise(u.Permissions)
|
||||
perms := ToEnterprise(u.Permissions)
|
||||
return c.Ctrl.SetUserPerms(ctx, u.Name, perms)
|
||||
}
|
||||
|
||||
// All is all users in influx
|
||||
func (c *Client) All(ctx context.Context) ([]chronograf.User, error) {
|
||||
func (c *UserStore) All(ctx context.Context) ([]chronograf.User, error) {
|
||||
all, err := c.Ctrl.Users(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -53,13 +59,14 @@ func (c *Client) All(ctx context.Context) ([]chronograf.User, error) {
|
|||
for i, user := range all.Users {
|
||||
res[i] = chronograf.User{
|
||||
Name: user.Name,
|
||||
Permissions: toChronograf(user.Permissions),
|
||||
Permissions: ToChronograf(user.Permissions),
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func toEnterprise(perms chronograf.Permissions) Permissions {
|
||||
// ToEnterprise converts chronograf permission shape to enterprise
|
||||
func ToEnterprise(perms chronograf.Permissions) Permissions {
|
||||
res := Permissions{}
|
||||
for _, perm := range perms {
|
||||
if perm.Scope == chronograf.AllScope {
|
||||
|
@ -72,7 +79,8 @@ func toEnterprise(perms chronograf.Permissions) Permissions {
|
|||
return res
|
||||
}
|
||||
|
||||
func toChronograf(perms Permissions) chronograf.Permissions {
|
||||
// ToChronograf converts enterprise permissions shape to chronograf shape
|
||||
func ToChronograf(perms Permissions) chronograf.Permissions {
|
||||
res := chronograf.Permissions{}
|
||||
for db, perm := range perms {
|
||||
// Enterprise uses empty string as the key for all databases
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package enterprise
|
||||
package enterprise_test
|
||||
|
||||
import (
|
||||
"container/ring"
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
"github.com/influxdata/chronograf/enterprise"
|
||||
)
|
||||
|
||||
func TestClient_Add(t *testing.T) {
|
||||
|
@ -67,7 +67,7 @@ func TestClient_Add(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
c := &Client{
|
||||
c := &enterprise.UserStore{
|
||||
Ctrl: tt.fields.Ctrl,
|
||||
Logger: tt.fields.Logger,
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ func TestClient_Delete(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
c := &Client{
|
||||
c := &enterprise.UserStore{
|
||||
Ctrl: tt.fields.Ctrl,
|
||||
Logger: tt.fields.Logger,
|
||||
}
|
||||
|
@ -146,10 +146,8 @@ func TestClient_Delete(t *testing.T) {
|
|||
|
||||
func TestClient_Get(t *testing.T) {
|
||||
type fields struct {
|
||||
Ctrl *mockCtrl
|
||||
Logger chronograf.Logger
|
||||
dataNodes *ring.Ring
|
||||
opened bool
|
||||
Ctrl *mockCtrl
|
||||
Logger chronograf.Logger
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
|
@ -166,8 +164,8 @@ func TestClient_Get(t *testing.T) {
|
|||
name: "Successful Get User",
|
||||
fields: fields{
|
||||
Ctrl: &mockCtrl{
|
||||
user: func(ctx context.Context, name string) (*User, error) {
|
||||
return &User{
|
||||
user: func(ctx context.Context, name string) (*enterprise.User, error) {
|
||||
return &enterprise.User{
|
||||
Name: "marty",
|
||||
Password: "johnny be good",
|
||||
Permissions: map[string][]string{
|
||||
|
@ -199,7 +197,7 @@ func TestClient_Get(t *testing.T) {
|
|||
name: "Failure to get User",
|
||||
fields: fields{
|
||||
Ctrl: &mockCtrl{
|
||||
user: func(ctx context.Context, name string) (*User, error) {
|
||||
user: func(ctx context.Context, name string) (*enterprise.User, error) {
|
||||
return nil, fmt.Errorf("1.21 Gigawatts! Tom, how could I have been so careless?")
|
||||
},
|
||||
},
|
||||
|
@ -212,11 +210,9 @@ func TestClient_Get(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
c := &Client{
|
||||
Ctrl: tt.fields.Ctrl,
|
||||
Logger: tt.fields.Logger,
|
||||
dataNodes: tt.fields.dataNodes,
|
||||
opened: tt.fields.opened,
|
||||
c := &enterprise.UserStore{
|
||||
Ctrl: tt.fields.Ctrl,
|
||||
Logger: tt.fields.Logger,
|
||||
}
|
||||
got, err := c.Get(tt.args.ctx, tt.args.name)
|
||||
if (err != nil) != tt.wantErr {
|
||||
|
@ -283,7 +279,7 @@ func TestClient_Update(t *testing.T) {
|
|||
name: "Success setting permissions User",
|
||||
fields: fields{
|
||||
Ctrl: &mockCtrl{
|
||||
setUserPerms: func(ctx context.Context, name string, perms Permissions) error {
|
||||
setUserPerms: func(ctx context.Context, name string, perms enterprise.Permissions) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
|
@ -306,7 +302,7 @@ func TestClient_Update(t *testing.T) {
|
|||
name: "Failure setting permissions User",
|
||||
fields: fields{
|
||||
Ctrl: &mockCtrl{
|
||||
setUserPerms: func(ctx context.Context, name string, perms Permissions) error {
|
||||
setUserPerms: func(ctx context.Context, name string, perms enterprise.Permissions) error {
|
||||
return fmt.Errorf("They found me, I don't know how, but they found me.")
|
||||
},
|
||||
},
|
||||
|
@ -327,7 +323,7 @@ func TestClient_Update(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
c := &Client{
|
||||
c := &enterprise.UserStore{
|
||||
Ctrl: tt.fields.Ctrl,
|
||||
Logger: tt.fields.Logger,
|
||||
}
|
||||
|
@ -356,9 +352,9 @@ func TestClient_All(t *testing.T) {
|
|||
name: "Successful Get User",
|
||||
fields: fields{
|
||||
Ctrl: &mockCtrl{
|
||||
users: func(ctx context.Context, name *string) (*Users, error) {
|
||||
return &Users{
|
||||
Users: []User{
|
||||
users: func(ctx context.Context, name *string) (*enterprise.Users, error) {
|
||||
return &enterprise.Users{
|
||||
Users: []enterprise.User{
|
||||
{
|
||||
Name: "marty",
|
||||
Password: "johnny be good",
|
||||
|
@ -394,7 +390,7 @@ func TestClient_All(t *testing.T) {
|
|||
name: "Failure to get User",
|
||||
fields: fields{
|
||||
Ctrl: &mockCtrl{
|
||||
users: func(ctx context.Context, name *string) (*Users, error) {
|
||||
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?")
|
||||
},
|
||||
},
|
||||
|
@ -406,7 +402,7 @@ func TestClient_All(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
c := &Client{
|
||||
c := &enterprise.UserStore{
|
||||
Ctrl: tt.fields.Ctrl,
|
||||
Logger: tt.fields.Logger,
|
||||
}
|
||||
|
@ -421,15 +417,15 @@ func TestClient_All(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_toEnterprise(t *testing.T) {
|
||||
func Test_ToEnterprise(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
perms chronograf.Permissions
|
||||
want Permissions
|
||||
want enterprise.Permissions
|
||||
}{
|
||||
{
|
||||
name: "All Scopes",
|
||||
want: Permissions{"": []string{"ViewChronograf", "KapacitorAPI"}},
|
||||
want: enterprise.Permissions{"": []string{"ViewChronograf", "KapacitorAPI"}},
|
||||
perms: chronograf.Permissions{
|
||||
{
|
||||
Scope: chronograf.AllScope,
|
||||
|
@ -439,7 +435,7 @@ func Test_toEnterprise(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "DB Scope",
|
||||
want: Permissions{"telegraf": []string{"ReadData", "WriteData"}},
|
||||
want: enterprise.Permissions{"telegraf": []string{"ReadData", "WriteData"}},
|
||||
perms: chronograf.Permissions{
|
||||
{
|
||||
Scope: chronograf.DBScope,
|
||||
|
@ -450,21 +446,21 @@ func Test_toEnterprise(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := toEnterprise(tt.perms); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("%q. toEnterprise() = %v, want %v", tt.name, got, tt.want)
|
||||
if got := enterprise.ToEnterprise(tt.perms); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("%q. ToEnterprise() = %v, want %v", tt.name, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_toChronograf(t *testing.T) {
|
||||
func Test_ToChronograf(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
perms Permissions
|
||||
perms enterprise.Permissions
|
||||
want chronograf.Permissions
|
||||
}{
|
||||
{
|
||||
name: "All Scopes",
|
||||
perms: Permissions{"": []string{"ViewChronograf", "KapacitorAPI"}},
|
||||
perms: enterprise.Permissions{"": []string{"ViewChronograf", "KapacitorAPI"}},
|
||||
want: chronograf.Permissions{
|
||||
{
|
||||
Scope: chronograf.AllScope,
|
||||
|
@ -474,7 +470,7 @@ func Test_toChronograf(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "DB Scope",
|
||||
perms: Permissions{"telegraf": []string{"ReadData", "WriteData"}},
|
||||
perms: enterprise.Permissions{"telegraf": []string{"ReadData", "WriteData"}},
|
||||
want: chronograf.Permissions{
|
||||
{
|
||||
Scope: chronograf.DBScope,
|
||||
|
@ -485,26 +481,33 @@ func Test_toChronograf(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := toChronograf(tt.perms); !reflect.DeepEqual(got, tt.want) {
|
||||
if got := enterprise.ToChronograf(tt.perms); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("%q. toChronograf() = %v, want %v", tt.name, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type mockCtrl struct {
|
||||
showCluster func(ctx context.Context) (*Cluster, error)
|
||||
user func(ctx context.Context, name string) (*User, error)
|
||||
showCluster func(ctx context.Context) (*enterprise.Cluster, error)
|
||||
user func(ctx context.Context, name string) (*enterprise.User, error)
|
||||
createUser func(ctx context.Context, name, passwd string) error
|
||||
deleteUser func(ctx context.Context, name string) error
|
||||
changePassword func(ctx context.Context, name, passwd string) error
|
||||
users func(ctx context.Context, name *string) (*Users, error)
|
||||
setUserPerms func(ctx context.Context, name string, perms Permissions) error
|
||||
users func(ctx context.Context, name *string) (*enterprise.Users, error)
|
||||
setUserPerms func(ctx context.Context, name string, perms enterprise.Permissions) error
|
||||
|
||||
roles func(ctx context.Context, name *string) (*enterprise.Roles, error)
|
||||
role func(ctx context.Context, name string) (*enterprise.Role, error)
|
||||
createRole func(ctx context.Context, name string) error
|
||||
deleteRole func(ctx context.Context, name string) error
|
||||
setRolePerms func(ctx context.Context, name string, perms enterprise.Permissions) error
|
||||
setRoleUsers func(ctx context.Context, name string, users []string) error
|
||||
}
|
||||
|
||||
func (m *mockCtrl) ShowCluster(ctx context.Context) (*Cluster, error) {
|
||||
func (m *mockCtrl) ShowCluster(ctx context.Context) (*enterprise.Cluster, error) {
|
||||
return m.showCluster(ctx)
|
||||
}
|
||||
func (m *mockCtrl) User(ctx context.Context, name string) (*User, error) {
|
||||
func (m *mockCtrl) User(ctx context.Context, name string) (*enterprise.User, error) {
|
||||
return m.user(ctx, name)
|
||||
}
|
||||
func (m *mockCtrl) CreateUser(ctx context.Context, name, passwd string) error {
|
||||
|
@ -516,9 +519,33 @@ func (m *mockCtrl) DeleteUser(ctx context.Context, name string) error {
|
|||
func (m *mockCtrl) ChangePassword(ctx context.Context, name, passwd string) error {
|
||||
return m.changePassword(ctx, name, passwd)
|
||||
}
|
||||
func (m *mockCtrl) Users(ctx context.Context, name *string) (*Users, error) {
|
||||
func (m *mockCtrl) Users(ctx context.Context, name *string) (*enterprise.Users, error) {
|
||||
return m.users(ctx, name)
|
||||
}
|
||||
func (m *mockCtrl) SetUserPerms(ctx context.Context, name string, perms Permissions) error {
|
||||
func (m *mockCtrl) SetUserPerms(ctx context.Context, name string, perms enterprise.Permissions) error {
|
||||
return m.setUserPerms(ctx, name, perms)
|
||||
}
|
||||
|
||||
func (m *mockCtrl) Roles(ctx context.Context, name *string) (*enterprise.Roles, error) {
|
||||
return m.roles(ctx, name)
|
||||
}
|
||||
|
||||
func (m *mockCtrl) Role(ctx context.Context, name string) (*enterprise.Role, error) {
|
||||
return m.role(ctx, name)
|
||||
}
|
||||
|
||||
func (m *mockCtrl) CreateRole(ctx context.Context, name string) error {
|
||||
return m.createRole(ctx, name)
|
||||
}
|
||||
|
||||
func (m *mockCtrl) DeleteRole(ctx context.Context, name string) error {
|
||||
return m.deleteRole(ctx, name)
|
||||
}
|
||||
|
||||
func (m *mockCtrl) SetRolePerms(ctx context.Context, name string, perms enterprise.Permissions) error {
|
||||
return m.setRolePerms(ctx, name, perms)
|
||||
}
|
||||
|
||||
func (m *mockCtrl) SetRoleUsers(ctx context.Context, name string, users []string) error {
|
||||
return m.setRoleUsers(ctx, name, users)
|
||||
}
|
||||
|
|
|
@ -172,3 +172,8 @@ func (c *Client) Connect(ctx context.Context, src *chronograf.Source) error {
|
|||
func (c *Client) Users(ctx context.Context) chronograf.UsersStore {
|
||||
return c
|
||||
}
|
||||
|
||||
// Roles aren't support in OSS
|
||||
func (c *Client) Roles(ctx context.Context) (chronograf.RolesStore, error) {
|
||||
return nil, fmt.Errorf("Roles not support in open-source InfluxDB. Roles are support in Influx Enterprise")
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package influx_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
@ -9,7 +10,6 @@ import (
|
|||
"github.com/influxdata/chronograf"
|
||||
"github.com/influxdata/chronograf/influx"
|
||||
"github.com/influxdata/chronograf/log"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func Test_Influx_MakesRequestsToQueryEndpoint(t *testing.T) {
|
||||
|
@ -204,3 +204,11 @@ func Test_Influx_ReportsInfluxErrs(t *testing.T) {
|
|||
t.Fatal("Expected an error but received none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_Roles(t *testing.T) {
|
||||
c := &influx.Client{}
|
||||
_, err := c.Roles(context.Background())
|
||||
if err == nil {
|
||||
t.Errorf("Client.Roles() want error")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
var _ chronograf.RolesStore = &RolesStore{}
|
||||
|
||||
// RolesStore mock allows all functions to be set for testing
|
||||
type RolesStore struct {
|
||||
AllF func(context.Context) ([]chronograf.Role, error)
|
||||
AddF func(context.Context, *chronograf.Role) (*chronograf.Role, error)
|
||||
DeleteF func(context.Context, *chronograf.Role) error
|
||||
GetF func(ctx context.Context, name string) (*chronograf.Role, error)
|
||||
UpdateF func(context.Context, *chronograf.Role) error
|
||||
}
|
||||
|
||||
// All lists all Roles from the RolesStore
|
||||
func (s *RolesStore) All(ctx context.Context) ([]chronograf.Role, error) {
|
||||
return s.AllF(ctx)
|
||||
}
|
||||
|
||||
// Add a new Role in the RolesStore
|
||||
func (s *RolesStore) Add(ctx context.Context, u *chronograf.Role) (*chronograf.Role, error) {
|
||||
return s.AddF(ctx, u)
|
||||
}
|
||||
|
||||
// Delete the Role from the RolesStore
|
||||
func (s *RolesStore) Delete(ctx context.Context, u *chronograf.Role) error {
|
||||
return s.DeleteF(ctx, u)
|
||||
}
|
||||
|
||||
// Get retrieves a Role if name exists.
|
||||
func (s *RolesStore) Get(ctx context.Context, name string) (*chronograf.Role, error) {
|
||||
return s.GetF(ctx, name)
|
||||
}
|
||||
|
||||
// Update the Role's permissions or users
|
||||
func (s *RolesStore) Update(ctx context.Context, u *chronograf.Role) error {
|
||||
return s.UpdateF(ctx, u)
|
||||
}
|
|
@ -18,6 +18,8 @@ type TimeSeries struct {
|
|||
UsersF func(context.Context) chronograf.UsersStore
|
||||
// Allowances returns all valid names permissions in this database
|
||||
AllowancesF func(context.Context) chronograf.Allowances
|
||||
// RolesF represents the roles. Roles group permissions and Users
|
||||
RolesF func(context.Context) (chronograf.RolesStore, error)
|
||||
}
|
||||
|
||||
// Query retrieves time series data from the database.
|
||||
|
@ -35,6 +37,11 @@ func (t *TimeSeries) Users(ctx context.Context) chronograf.UsersStore {
|
|||
return t.UsersF(ctx)
|
||||
}
|
||||
|
||||
// Roles represents the roles. Roles group permissions and Users
|
||||
func (t *TimeSeries) Roles(ctx context.Context) (chronograf.RolesStore, error) {
|
||||
return t.RolesF(ctx)
|
||||
}
|
||||
|
||||
// Allowances returns all valid names permissions in this database
|
||||
func (t *TimeSeries) Allowances(ctx context.Context) chronograf.Allowances {
|
||||
return t.AllowancesF(ctx)
|
||||
|
|
308
server/admin.go
308
server/admin.go
|
@ -11,8 +11,23 @@ import (
|
|||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
func validPermissions(perms *chronograf.Permissions) error {
|
||||
if perms == nil {
|
||||
return nil
|
||||
}
|
||||
for _, perm := range *perms {
|
||||
if perm.Scope != chronograf.AllScope && perm.Scope != chronograf.DBScope {
|
||||
return fmt.Errorf("Invalid permission scope")
|
||||
}
|
||||
if perm.Scope == chronograf.DBScope && perm.Name == "" {
|
||||
return fmt.Errorf("Database scoped permission requires a name")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type sourceUserRequest struct {
|
||||
Username string `json:"username,omitempty"` // Username for new account
|
||||
Username string `json:"name,omitempty"` // Username for new account
|
||||
Password string `json:"password,omitempty"` // Password for new account
|
||||
Permissions chronograf.Permissions `json:"permissions,omitempty"` // Optional permissions
|
||||
}
|
||||
|
@ -24,40 +39,35 @@ func (r *sourceUserRequest) ValidCreate() error {
|
|||
if r.Password == "" {
|
||||
return fmt.Errorf("Password required")
|
||||
}
|
||||
return nil
|
||||
return validPermissions(&r.Permissions)
|
||||
}
|
||||
|
||||
func (r *sourceUserRequest) ValidUpdate() error {
|
||||
if r.Password == "" && len(r.Permissions) == 0 {
|
||||
return fmt.Errorf("No fields to update")
|
||||
}
|
||||
return nil
|
||||
return validPermissions(&r.Permissions)
|
||||
}
|
||||
|
||||
type sourceUser struct {
|
||||
Username string `json:"username,omitempty"` // Username for new account
|
||||
Username string `json:"name,omitempty"` // Username for new account
|
||||
Permissions chronograf.Permissions `json:"permissions,omitempty"` // Account's permissions
|
||||
Links sourceUserLinks `json:"links"` // Links are URI locations related to user
|
||||
Links selfLinks `json:"links"` // Links are URI locations related to user
|
||||
}
|
||||
|
||||
// newSourceUser creates a new user in the InfluxDB data source
|
||||
func newSourceUser(srcID int, name string, perms chronograf.Permissions) sourceUser {
|
||||
u := &url.URL{Path: name}
|
||||
encodedUser := u.String()
|
||||
httpAPISrcs := "/chronograf/v1/sources"
|
||||
return sourceUser{
|
||||
Username: name,
|
||||
Permissions: perms,
|
||||
Links: sourceUserLinks{
|
||||
Self: fmt.Sprintf("%s/%d/users/%s", httpAPISrcs, srcID, encodedUser),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type sourceUserLinks struct {
|
||||
type selfLinks struct {
|
||||
Self string `json:"self"` // Self link mapping to this resource
|
||||
}
|
||||
|
||||
func newSelfLinks(id int, parent, resource string) selfLinks {
|
||||
httpAPISrcs := "/chronograf/v1/sources"
|
||||
u := &url.URL{Path: resource}
|
||||
encodedResource := u.String()
|
||||
return selfLinks{
|
||||
Self: fmt.Sprintf("%s/%d/%s/%s", httpAPISrcs, id, parent, encodedResource),
|
||||
}
|
||||
}
|
||||
|
||||
// NewSourceUser adds user to source
|
||||
func (h *Service) NewSourceUser(w http.ResponseWriter, r *http.Request) {
|
||||
var req sourceUserRequest
|
||||
|
@ -88,7 +98,11 @@ func (h *Service) NewSourceUser(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
su := newSourceUser(srcID, res.Name, req.Permissions)
|
||||
su := sourceUser{
|
||||
Username: res.Name,
|
||||
Permissions: req.Permissions,
|
||||
Links: newSelfLinks(srcID, "users", res.Name),
|
||||
}
|
||||
w.Header().Add("Location", su.Links.Self)
|
||||
encodeJSON(w, http.StatusCreated, su, h.Logger)
|
||||
}
|
||||
|
@ -114,7 +128,11 @@ func (h *Service) SourceUsers(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
su := []sourceUser{}
|
||||
for _, u := range users {
|
||||
su = append(su, newSourceUser(srcID, u.Name, u.Permissions))
|
||||
su = append(su, sourceUser{
|
||||
Username: u.Name,
|
||||
Permissions: u.Permissions,
|
||||
Links: newSelfLinks(srcID, "users", u.Name),
|
||||
})
|
||||
}
|
||||
|
||||
res := sourceUsers{
|
||||
|
@ -140,7 +158,11 @@ func (h *Service) SourceUserID(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
res := newSourceUser(srcID, u.Name, u.Permissions)
|
||||
res := sourceUser{
|
||||
Username: u.Name,
|
||||
Permissions: u.Permissions,
|
||||
Links: newSelfLinks(srcID, "users", u.Name),
|
||||
}
|
||||
encodeJSON(w, http.StatusOK, res, h.Logger)
|
||||
}
|
||||
|
||||
|
@ -192,27 +214,39 @@ func (h *Service) UpdateSourceUser(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
su := newSourceUser(srcID, user.Name, user.Permissions)
|
||||
su := sourceUser{
|
||||
Username: user.Name,
|
||||
Permissions: user.Permissions,
|
||||
Links: newSelfLinks(srcID, "users", user.Name),
|
||||
}
|
||||
w.Header().Add("Location", su.Links.Self)
|
||||
encodeJSON(w, http.StatusOK, su, h.Logger)
|
||||
}
|
||||
|
||||
func (h *Service) sourceUsersStore(ctx context.Context, w http.ResponseWriter, r *http.Request) (int, chronograf.UsersStore, error) {
|
||||
func (h *Service) sourcesSeries(ctx context.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
srcID, err := paramID("id", r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, err.Error(), h.Logger)
|
||||
return 0, nil, err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
src, err := h.SourcesStore.Get(ctx, srcID)
|
||||
if err != nil {
|
||||
notFound(w, srcID, h.Logger)
|
||||
return 0, nil, err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if err = h.TimeSeries.Connect(ctx, &src); err != nil {
|
||||
msg := fmt.Sprintf("Unable to connect to source %d", srcID)
|
||||
Error(w, http.StatusBadRequest, msg, h.Logger)
|
||||
return 0, err
|
||||
}
|
||||
return srcID, nil
|
||||
}
|
||||
|
||||
func (h *Service) sourceUsersStore(ctx context.Context, w http.ResponseWriter, r *http.Request) (int, chronograf.UsersStore, error) {
|
||||
srcID, err := h.sourcesSeries(ctx, w, r)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
|
@ -220,6 +254,15 @@ func (h *Service) sourceUsersStore(ctx context.Context, w http.ResponseWriter, r
|
|||
return srcID, store, nil
|
||||
}
|
||||
|
||||
// hasRoles checks if the influx source has roles or not
|
||||
func (h *Service) hasRoles(ctx context.Context) (chronograf.RolesStore, bool) {
|
||||
store, err := h.TimeSeries.Roles(ctx)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return store, true
|
||||
}
|
||||
|
||||
// Permissions returns all possible permissions for this source.
|
||||
func (h *Service) Permissions(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
@ -259,3 +302,214 @@ func (h *Service) Permissions(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
encodeJSON(w, http.StatusOK, res, h.Logger)
|
||||
}
|
||||
|
||||
type sourceRoleRequest struct {
|
||||
chronograf.Role
|
||||
}
|
||||
|
||||
func (r *sourceRoleRequest) ValidCreate() error {
|
||||
if r.Name == "" || len(r.Name) > 254 {
|
||||
return fmt.Errorf("Name is required for a role")
|
||||
}
|
||||
for _, user := range r.Users {
|
||||
if user.Name == "" {
|
||||
return fmt.Errorf("Username required")
|
||||
}
|
||||
}
|
||||
return validPermissions(&r.Permissions)
|
||||
}
|
||||
|
||||
func (r *sourceRoleRequest) ValidUpdate() error {
|
||||
if len(r.Name) > 254 {
|
||||
return fmt.Errorf("Username too long; must be less than 254 characters")
|
||||
}
|
||||
for _, user := range r.Users {
|
||||
if user.Name == "" {
|
||||
return fmt.Errorf("Username required")
|
||||
}
|
||||
}
|
||||
return validPermissions(&r.Permissions)
|
||||
}
|
||||
|
||||
type roleResponse struct {
|
||||
Users []sourceUser `json:"users"`
|
||||
Name string `json:"name"`
|
||||
Permissions chronograf.Permissions `json:"permissions"`
|
||||
Links selfLinks `json:"links"`
|
||||
}
|
||||
|
||||
func newRoleResponse(srcID int, res *chronograf.Role) roleResponse {
|
||||
su := make([]sourceUser, len(res.Users))
|
||||
for i := range res.Users {
|
||||
name := res.Users[i].Name
|
||||
su[i] = sourceUser{
|
||||
Username: name,
|
||||
Links: newSelfLinks(srcID, "users", name),
|
||||
}
|
||||
}
|
||||
|
||||
if res.Permissions == nil {
|
||||
res.Permissions = make(chronograf.Permissions, 0)
|
||||
}
|
||||
return roleResponse{
|
||||
Name: res.Name,
|
||||
Permissions: res.Permissions,
|
||||
Users: su,
|
||||
Links: newSelfLinks(srcID, "roles", res.Name),
|
||||
}
|
||||
}
|
||||
|
||||
// NewRole adds role to source
|
||||
func (h *Service) NewRole(w http.ResponseWriter, r *http.Request) {
|
||||
var req sourceRoleRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
invalidJSON(w, h.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
if err := req.ValidCreate(); err != nil {
|
||||
invalidData(w, err, h.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
srcID, err := h.sourcesSeries(ctx, w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
roles, ok := h.hasRoles(ctx)
|
||||
if !ok {
|
||||
Error(w, http.StatusNotFound, fmt.Sprintf("Source %d does not have role capability", srcID), h.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := roles.Add(ctx, &req.Role)
|
||||
if err != nil {
|
||||
Error(w, http.StatusBadRequest, err.Error(), h.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
rr := newRoleResponse(srcID, res)
|
||||
w.Header().Add("Location", rr.Links.Self)
|
||||
encodeJSON(w, http.StatusCreated, rr, h.Logger)
|
||||
}
|
||||
|
||||
// UpdateRole changes the permissions or users of a role
|
||||
func (h *Service) UpdateRole(w http.ResponseWriter, r *http.Request) {
|
||||
var req sourceRoleRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
invalidJSON(w, h.Logger)
|
||||
return
|
||||
}
|
||||
if err := req.ValidUpdate(); err != nil {
|
||||
invalidData(w, err, h.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
srcID, err := h.sourcesSeries(ctx, w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
roles, ok := h.hasRoles(ctx)
|
||||
if !ok {
|
||||
Error(w, http.StatusNotFound, fmt.Sprintf("Source %d does not have role capability", srcID), h.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
rid := httprouter.GetParamFromContext(ctx, "rid")
|
||||
req.Name = rid
|
||||
|
||||
if err := roles.Update(ctx, &req.Role); err != nil {
|
||||
Error(w, http.StatusBadRequest, err.Error(), h.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
role, err := roles.Get(ctx, req.Name)
|
||||
if err != nil {
|
||||
Error(w, http.StatusBadRequest, err.Error(), h.Logger)
|
||||
return
|
||||
}
|
||||
rr := newRoleResponse(srcID, role)
|
||||
w.Header().Add("Location", rr.Links.Self)
|
||||
encodeJSON(w, http.StatusOK, rr, h.Logger)
|
||||
}
|
||||
|
||||
// RoleID retrieves a role with ID from store.
|
||||
func (h *Service) RoleID(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
srcID, err := h.sourcesSeries(ctx, w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
roles, ok := h.hasRoles(ctx)
|
||||
if !ok {
|
||||
Error(w, http.StatusNotFound, fmt.Sprintf("Source %d does not have role capability", srcID), h.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
rid := httprouter.GetParamFromContext(ctx, "rid")
|
||||
role, err := roles.Get(ctx, rid)
|
||||
if err != nil {
|
||||
Error(w, http.StatusBadRequest, err.Error(), h.Logger)
|
||||
return
|
||||
}
|
||||
rr := newRoleResponse(srcID, role)
|
||||
encodeJSON(w, http.StatusOK, rr, h.Logger)
|
||||
}
|
||||
|
||||
// Roles retrieves all roles from the store
|
||||
func (h *Service) Roles(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
srcID, err := h.sourcesSeries(ctx, w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
store, ok := h.hasRoles(ctx)
|
||||
if !ok {
|
||||
Error(w, http.StatusNotFound, fmt.Sprintf("Source %d does not have role capability", srcID), h.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
roles, err := store.All(ctx)
|
||||
if err != nil {
|
||||
Error(w, http.StatusBadRequest, err.Error(), h.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
rr := make([]roleResponse, len(roles))
|
||||
for i, role := range roles {
|
||||
rr[i] = newRoleResponse(srcID, &role)
|
||||
}
|
||||
|
||||
res := struct {
|
||||
Roles []roleResponse `json:"roles"`
|
||||
}{rr}
|
||||
encodeJSON(w, http.StatusOK, res, h.Logger)
|
||||
}
|
||||
|
||||
// RemoveRole removes role from data source.
|
||||
func (h *Service) RemoveRole(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
srcID, err := h.sourcesSeries(ctx, w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
roles, ok := h.hasRoles(ctx)
|
||||
if !ok {
|
||||
Error(w, http.StatusNotFound, fmt.Sprintf("Source %d does not have role capability", srcID), h.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
rid := httprouter.GetParamFromContext(ctx, "rid")
|
||||
if err := roles.Delete(ctx, &chronograf.Role{Name: rid}); err != nil {
|
||||
Error(w, http.StatusBadRequest, err.Error(), h.Logger)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/bouk/httprouter"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
"github.com/influxdata/chronograf/log"
|
||||
"github.com/influxdata/chronograf/mocks"
|
||||
|
@ -45,7 +44,7 @@ func TestService_NewSourceUser(t *testing.T) {
|
|||
"POST",
|
||||
"http://server.local/chronograf/v1/sources/1",
|
||||
ioutil.NopCloser(
|
||||
bytes.NewReader([]byte(`{"username": "marty", "password": "the_lake"}`)))),
|
||||
bytes.NewReader([]byte(`{"name": "marty", "password": "the_lake"}`)))),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
|
@ -55,7 +54,7 @@ func TestService_NewSourceUser(t *testing.T) {
|
|||
return chronograf.Source{
|
||||
ID: 1,
|
||||
Name: "muh source",
|
||||
Username: "username",
|
||||
Username: "name",
|
||||
Password: "hunter2",
|
||||
URL: "http://localhost:8086",
|
||||
}, nil
|
||||
|
@ -77,7 +76,7 @@ func TestService_NewSourceUser(t *testing.T) {
|
|||
ID: "1",
|
||||
wantStatus: http.StatusCreated,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"username":"marty","links":{"self":"/chronograf/v1/sources/1/users/marty"}}
|
||||
wantBody: `{"name":"marty","links":{"self":"/chronograf/v1/sources/1/users/marty"}}
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -88,7 +87,7 @@ func TestService_NewSourceUser(t *testing.T) {
|
|||
"POST",
|
||||
"http://server.local/chronograf/v1/sources/1",
|
||||
ioutil.NopCloser(
|
||||
bytes.NewReader([]byte(`{"username": "marty", "password": "the_lake"}`)))),
|
||||
bytes.NewReader([]byte(`{"name": "marty", "password": "the_lake"}`)))),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
|
@ -98,7 +97,7 @@ func TestService_NewSourceUser(t *testing.T) {
|
|||
return chronograf.Source{
|
||||
ID: 1,
|
||||
Name: "muh source",
|
||||
Username: "username",
|
||||
Username: "name",
|
||||
Password: "hunter2",
|
||||
URL: "http://localhost:8086",
|
||||
}, nil
|
||||
|
@ -130,7 +129,7 @@ func TestService_NewSourceUser(t *testing.T) {
|
|||
"POST",
|
||||
"http://server.local/chronograf/v1/sources/1",
|
||||
ioutil.NopCloser(
|
||||
bytes.NewReader([]byte(`{"username": "marty", "password": "the_lake"}`)))),
|
||||
bytes.NewReader([]byte(`{"name": "marty", "password": "the_lake"}`)))),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
|
@ -140,7 +139,7 @@ func TestService_NewSourceUser(t *testing.T) {
|
|||
return chronograf.Source{
|
||||
ID: 1,
|
||||
Name: "muh source",
|
||||
Username: "username",
|
||||
Username: "name",
|
||||
Password: "hunter2",
|
||||
URL: "http://localhost:8086",
|
||||
}, nil
|
||||
|
@ -165,7 +164,7 @@ func TestService_NewSourceUser(t *testing.T) {
|
|||
"POST",
|
||||
"http://server.local/chronograf/v1/sources/1",
|
||||
ioutil.NopCloser(
|
||||
bytes.NewReader([]byte(`{"username": "marty", "password": "the_lake"}`)))),
|
||||
bytes.NewReader([]byte(`{"name": "marty", "password": "the_lake"}`)))),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
|
@ -189,7 +188,7 @@ func TestService_NewSourceUser(t *testing.T) {
|
|||
"POST",
|
||||
"http://server.local/chronograf/v1/sources/1",
|
||||
ioutil.NopCloser(
|
||||
bytes.NewReader([]byte(`{"username": "marty", "password": "the_lake"}`)))),
|
||||
bytes.NewReader([]byte(`{"name": "marty", "password": "the_lake"}`)))),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
|
@ -201,7 +200,7 @@ func TestService_NewSourceUser(t *testing.T) {
|
|||
wantBody: `{"code":422,"message":"Error converting ID BAD"}`,
|
||||
},
|
||||
{
|
||||
name: "Bad username",
|
||||
name: "Bad name",
|
||||
args: args{
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest(
|
||||
|
@ -311,7 +310,7 @@ func TestService_SourceUsers(t *testing.T) {
|
|||
return chronograf.Source{
|
||||
ID: 1,
|
||||
Name: "muh source",
|
||||
Username: "username",
|
||||
Username: "name",
|
||||
Password: "hunter2",
|
||||
URL: "http://localhost:8086",
|
||||
}, nil
|
||||
|
@ -344,7 +343,7 @@ func TestService_SourceUsers(t *testing.T) {
|
|||
ID: "1",
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"users":[{"username":"strickland","permissions":[{"scope":"all","allowed":["READ"]}],"links":{"self":"/chronograf/v1/sources/1/users/strickland"}}]}
|
||||
wantBody: `{"users":[{"name":"strickland","permissions":[{"scope":"all","allowed":["READ"]}],"links":{"self":"/chronograf/v1/sources/1/users/strickland"}}]}
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
@ -419,7 +418,7 @@ func TestService_SourceUserID(t *testing.T) {
|
|||
return chronograf.Source{
|
||||
ID: 1,
|
||||
Name: "muh source",
|
||||
Username: "username",
|
||||
Username: "name",
|
||||
Password: "hunter2",
|
||||
URL: "http://localhost:8086",
|
||||
}, nil
|
||||
|
@ -451,7 +450,7 @@ func TestService_SourceUserID(t *testing.T) {
|
|||
UID: "strickland",
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"username":"strickland","permissions":[{"scope":"all","allowed":["READ"]}],"links":{"self":"/chronograf/v1/sources/1/users/strickland"}}
|
||||
wantBody: `{"name":"strickland","permissions":[{"scope":"all","allowed":["READ"]}],"links":{"self":"/chronograf/v1/sources/1/users/strickland"}}
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
@ -526,7 +525,7 @@ func TestService_RemoveSourceUser(t *testing.T) {
|
|||
return chronograf.Source{
|
||||
ID: 1,
|
||||
Name: "muh source",
|
||||
Username: "username",
|
||||
Username: "name",
|
||||
Password: "hunter2",
|
||||
URL: "http://localhost:8086",
|
||||
}, nil
|
||||
|
@ -611,7 +610,7 @@ func TestService_UpdateSourceUser(t *testing.T) {
|
|||
"POST",
|
||||
"http://server.local/chronograf/v1/sources/1",
|
||||
ioutil.NopCloser(
|
||||
bytes.NewReader([]byte(`{"username": "marty", "password": "the_lake"}`)))),
|
||||
bytes.NewReader([]byte(`{"name": "marty", "password": "the_lake"}`)))),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
|
@ -621,7 +620,7 @@ func TestService_UpdateSourceUser(t *testing.T) {
|
|||
return chronograf.Source{
|
||||
ID: 1,
|
||||
Name: "muh source",
|
||||
Username: "username",
|
||||
Username: "name",
|
||||
Password: "hunter2",
|
||||
URL: "http://localhost:8086",
|
||||
}, nil
|
||||
|
@ -644,7 +643,7 @@ func TestService_UpdateSourceUser(t *testing.T) {
|
|||
UID: "marty",
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"username":"marty","links":{"self":"/chronograf/v1/sources/1/users/marty"}}
|
||||
wantBody: `{"name":"marty","links":{"self":"/chronograf/v1/sources/1/users/marty"}}
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -655,7 +654,7 @@ func TestService_UpdateSourceUser(t *testing.T) {
|
|||
"POST",
|
||||
"http://server.local/chronograf/v1/sources/1",
|
||||
ioutil.NopCloser(
|
||||
bytes.NewReader([]byte(`{"username": "marty"}`)))),
|
||||
bytes.NewReader([]byte(`{"name": "marty"}`)))),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
|
@ -732,7 +731,7 @@ func TestService_Permissions(t *testing.T) {
|
|||
"POST",
|
||||
"http://server.local/chronograf/v1/sources/1",
|
||||
ioutil.NopCloser(
|
||||
bytes.NewReader([]byte(`{"username": "marty", "password": "the_lake"}`)))),
|
||||
bytes.NewReader([]byte(`{"name": "marty", "password": "the_lake"}`)))),
|
||||
},
|
||||
fields: fields{
|
||||
UseAuth: true,
|
||||
|
@ -742,7 +741,7 @@ func TestService_Permissions(t *testing.T) {
|
|||
return chronograf.Source{
|
||||
ID: 1,
|
||||
Name: "muh source",
|
||||
Username: "username",
|
||||
Username: "name",
|
||||
Password: "hunter2",
|
||||
URL: "http://localhost:8086",
|
||||
}, nil
|
||||
|
@ -795,3 +794,678 @@ func TestService_Permissions(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_NewSourceRole(t *testing.T) {
|
||||
type fields struct {
|
||||
SourcesStore chronograf.SourcesStore
|
||||
TimeSeries chronograf.TimeSeries
|
||||
Logger chronograf.Logger
|
||||
}
|
||||
type args struct {
|
||||
w *httptest.ResponseRecorder
|
||||
r *http.Request
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
ID string
|
||||
wantStatus int
|
||||
wantContentType string
|
||||
wantBody string
|
||||
}{
|
||||
{
|
||||
name: "Bad JSON",
|
||||
args: args{
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest(
|
||||
"POST",
|
||||
"http://server.local/chronograf/v1/sources/1/roles",
|
||||
ioutil.NopCloser(
|
||||
bytes.NewReader([]byte(`{BAD}`)))),
|
||||
},
|
||||
fields: fields{
|
||||
Logger: log.New(log.DebugLevel),
|
||||
},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"code":400,"message":"Unparsable JSON"}`,
|
||||
},
|
||||
{
|
||||
name: "Invalid request",
|
||||
args: args{
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest(
|
||||
"POST",
|
||||
"http://server.local/chronograf/v1/sources/1/roles",
|
||||
ioutil.NopCloser(
|
||||
bytes.NewReader([]byte(`{"name": ""}`)))),
|
||||
},
|
||||
fields: fields{
|
||||
Logger: log.New(log.DebugLevel),
|
||||
},
|
||||
ID: "1",
|
||||
wantStatus: http.StatusUnprocessableEntity,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"code":422,"message":"Name is required for a role"}`,
|
||||
},
|
||||
{
|
||||
name: "Invalid source ID",
|
||||
args: args{
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest(
|
||||
"POST",
|
||||
"http://server.local/chronograf/v1/sources/1/roles",
|
||||
ioutil.NopCloser(
|
||||
bytes.NewReader([]byte(`{"name": "newrole"}`)))),
|
||||
},
|
||||
fields: fields{
|
||||
Logger: log.New(log.DebugLevel),
|
||||
},
|
||||
ID: "BADROLE",
|
||||
wantStatus: http.StatusUnprocessableEntity,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"code":422,"message":"Error converting ID BADROLE"}`,
|
||||
},
|
||||
{
|
||||
name: "Source doesn't support roles",
|
||||
args: args{
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest(
|
||||
"POST",
|
||||
"http://server.local/chronograf/v1/sources/1/roles",
|
||||
ioutil.NopCloser(
|
||||
bytes.NewReader([]byte(`{"name": "role"}`)))),
|
||||
},
|
||||
fields: fields{
|
||||
Logger: log.New(log.DebugLevel),
|
||||
SourcesStore: &mocks.SourcesStore{
|
||||
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
|
||||
return chronograf.Source{
|
||||
ID: 1,
|
||||
Name: "muh source",
|
||||
Username: "name",
|
||||
Password: "hunter2",
|
||||
URL: "http://localhost:8086",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
TimeSeries: &mocks.TimeSeries{
|
||||
ConnectF: func(ctx context.Context, src *chronograf.Source) error {
|
||||
return nil
|
||||
},
|
||||
RolesF: func(ctx context.Context) (chronograf.RolesStore, error) {
|
||||
return nil, fmt.Errorf("roles not supported")
|
||||
},
|
||||
},
|
||||
},
|
||||
ID: "1",
|
||||
wantStatus: http.StatusNotFound,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"code":404,"message":"Source 1 does not have role capability"}`,
|
||||
},
|
||||
{
|
||||
name: "Unable to add role to server",
|
||||
args: args{
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest(
|
||||
"POST",
|
||||
"http://server.local/chronograf/v1/sources/1/roles",
|
||||
ioutil.NopCloser(
|
||||
bytes.NewReader([]byte(`{"name": "role"}`)))),
|
||||
},
|
||||
fields: fields{
|
||||
Logger: log.New(log.DebugLevel),
|
||||
SourcesStore: &mocks.SourcesStore{
|
||||
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
|
||||
return chronograf.Source{
|
||||
ID: 1,
|
||||
Name: "muh source",
|
||||
Username: "name",
|
||||
Password: "hunter2",
|
||||
URL: "http://localhost:8086",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
TimeSeries: &mocks.TimeSeries{
|
||||
ConnectF: func(ctx context.Context, src *chronograf.Source) error {
|
||||
return nil
|
||||
},
|
||||
RolesF: func(ctx context.Context) (chronograf.RolesStore, error) {
|
||||
return &mocks.RolesStore{
|
||||
AddF: func(ctx context.Context, u *chronograf.Role) (*chronograf.Role, error) {
|
||||
return nil, fmt.Errorf("server had and issue")
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
ID: "1",
|
||||
wantStatus: http.StatusBadRequest,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"code":400,"message":"server had and issue"}`,
|
||||
},
|
||||
{
|
||||
name: "New role for data source",
|
||||
args: args{
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest(
|
||||
"POST",
|
||||
"http://server.local/chronograf/v1/sources/1/roles",
|
||||
ioutil.NopCloser(
|
||||
bytes.NewReader([]byte(`{"name": "biffsgang","users": [{"name": "match"},{"name": "skinhead"},{"name": "3-d"}]}`)))),
|
||||
},
|
||||
fields: fields{
|
||||
Logger: log.New(log.DebugLevel),
|
||||
SourcesStore: &mocks.SourcesStore{
|
||||
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
|
||||
return chronograf.Source{
|
||||
ID: 1,
|
||||
Name: "muh source",
|
||||
Username: "name",
|
||||
Password: "hunter2",
|
||||
URL: "http://localhost:8086",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
TimeSeries: &mocks.TimeSeries{
|
||||
ConnectF: func(ctx context.Context, src *chronograf.Source) error {
|
||||
return nil
|
||||
},
|
||||
RolesF: func(ctx context.Context) (chronograf.RolesStore, error) {
|
||||
return &mocks.RolesStore{
|
||||
AddF: func(ctx context.Context, u *chronograf.Role) (*chronograf.Role, error) {
|
||||
return u, nil
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
ID: "1",
|
||||
wantStatus: http.StatusCreated,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"users":[{"name":"match","links":{"self":"/chronograf/v1/sources/1/users/match"}},{"name":"skinhead","links":{"self":"/chronograf/v1/sources/1/users/skinhead"}},{"name":"3-d","links":{"self":"/chronograf/v1/sources/1/users/3-d"}}],"name":"biffsgang","permissions":[],"links":{"self":"/chronograf/v1/sources/1/roles/biffsgang"}}
|
||||
`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
h := &server.Service{
|
||||
SourcesStore: tt.fields.SourcesStore,
|
||||
TimeSeries: tt.fields.TimeSeries,
|
||||
Logger: tt.fields.Logger,
|
||||
}
|
||||
tt.args.r = tt.args.r.WithContext(httprouter.WithParams(
|
||||
context.Background(),
|
||||
httprouter.Params{
|
||||
{
|
||||
Key: "id",
|
||||
Value: tt.ID,
|
||||
},
|
||||
}))
|
||||
|
||||
h.NewRole(tt.args.w, tt.args.r)
|
||||
|
||||
resp := tt.args.w.Result()
|
||||
content := resp.Header.Get("Content-Type")
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != tt.wantStatus {
|
||||
t.Errorf("%q. NewRole() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus)
|
||||
}
|
||||
if tt.wantContentType != "" && content != tt.wantContentType {
|
||||
t.Errorf("%q. NewRole() = %v, want %v", tt.name, content, tt.wantContentType)
|
||||
}
|
||||
if tt.wantBody != "" && string(body) != tt.wantBody {
|
||||
t.Errorf("%q. NewRole() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_UpdateRole(t *testing.T) {
|
||||
type fields struct {
|
||||
SourcesStore chronograf.SourcesStore
|
||||
TimeSeries chronograf.TimeSeries
|
||||
Logger chronograf.Logger
|
||||
}
|
||||
type args struct {
|
||||
w *httptest.ResponseRecorder
|
||||
r *http.Request
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
ID string
|
||||
RoleID string
|
||||
wantStatus int
|
||||
wantContentType string
|
||||
wantBody string
|
||||
}{
|
||||
{
|
||||
name: "Update role for data source",
|
||||
args: args{
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest(
|
||||
"POST",
|
||||
"http://server.local/chronograf/v1/sources/1/roles",
|
||||
ioutil.NopCloser(
|
||||
bytes.NewReader([]byte(`{"name": "biffsgang","users": [{"name": "match"},{"name": "skinhead"},{"name": "3-d"}]}`)))),
|
||||
},
|
||||
fields: fields{
|
||||
Logger: log.New(log.DebugLevel),
|
||||
SourcesStore: &mocks.SourcesStore{
|
||||
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
|
||||
return chronograf.Source{
|
||||
ID: 1,
|
||||
Name: "muh source",
|
||||
Username: "name",
|
||||
Password: "hunter2",
|
||||
URL: "http://localhost:8086",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
TimeSeries: &mocks.TimeSeries{
|
||||
ConnectF: func(ctx context.Context, src *chronograf.Source) error {
|
||||
return nil
|
||||
},
|
||||
RolesF: func(ctx context.Context) (chronograf.RolesStore, error) {
|
||||
return &mocks.RolesStore{
|
||||
UpdateF: func(ctx context.Context, u *chronograf.Role) error {
|
||||
return nil
|
||||
},
|
||||
GetF: func(ctx context.Context, name string) (*chronograf.Role, error) {
|
||||
return &chronograf.Role{
|
||||
Name: "biffsgang",
|
||||
Users: []chronograf.User{
|
||||
{
|
||||
Name: "match",
|
||||
},
|
||||
{
|
||||
Name: "skinhead",
|
||||
},
|
||||
{
|
||||
Name: "3-d",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
ID: "1",
|
||||
RoleID: "biffsgang",
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"users":[{"name":"match","links":{"self":"/chronograf/v1/sources/1/users/match"}},{"name":"skinhead","links":{"self":"/chronograf/v1/sources/1/users/skinhead"}},{"name":"3-d","links":{"self":"/chronograf/v1/sources/1/users/3-d"}}],"name":"biffsgang","permissions":[],"links":{"self":"/chronograf/v1/sources/1/roles/biffsgang"}}
|
||||
`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
h := &server.Service{
|
||||
SourcesStore: tt.fields.SourcesStore,
|
||||
TimeSeries: tt.fields.TimeSeries,
|
||||
Logger: tt.fields.Logger,
|
||||
}
|
||||
|
||||
tt.args.r = tt.args.r.WithContext(httprouter.WithParams(
|
||||
context.Background(),
|
||||
httprouter.Params{
|
||||
{
|
||||
Key: "id",
|
||||
Value: tt.ID,
|
||||
},
|
||||
{
|
||||
Key: "rid",
|
||||
Value: tt.RoleID,
|
||||
},
|
||||
}))
|
||||
|
||||
h.UpdateRole(tt.args.w, tt.args.r)
|
||||
|
||||
resp := tt.args.w.Result()
|
||||
content := resp.Header.Get("Content-Type")
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != tt.wantStatus {
|
||||
t.Errorf("%q. NewRole() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus)
|
||||
}
|
||||
if tt.wantContentType != "" && content != tt.wantContentType {
|
||||
t.Errorf("%q. NewRole() = %v, want %v", tt.name, content, tt.wantContentType)
|
||||
}
|
||||
if tt.wantBody != "" && string(body) != tt.wantBody {
|
||||
t.Errorf("%q. NewRole() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_RoleID(t *testing.T) {
|
||||
type fields struct {
|
||||
SourcesStore chronograf.SourcesStore
|
||||
TimeSeries chronograf.TimeSeries
|
||||
Logger chronograf.Logger
|
||||
}
|
||||
type args struct {
|
||||
w *httptest.ResponseRecorder
|
||||
r *http.Request
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
ID string
|
||||
RoleID string
|
||||
wantStatus int
|
||||
wantContentType string
|
||||
wantBody string
|
||||
}{
|
||||
{
|
||||
name: "Get role for data source",
|
||||
args: args{
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest(
|
||||
"GET",
|
||||
"http://server.local/chronograf/v1/sources/1/roles/biffsgang",
|
||||
nil),
|
||||
},
|
||||
fields: fields{
|
||||
Logger: log.New(log.DebugLevel),
|
||||
SourcesStore: &mocks.SourcesStore{
|
||||
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
|
||||
return chronograf.Source{
|
||||
ID: 1,
|
||||
Name: "muh source",
|
||||
Username: "name",
|
||||
Password: "hunter2",
|
||||
URL: "http://localhost:8086",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
TimeSeries: &mocks.TimeSeries{
|
||||
ConnectF: func(ctx context.Context, src *chronograf.Source) error {
|
||||
return nil
|
||||
},
|
||||
RolesF: func(ctx context.Context) (chronograf.RolesStore, error) {
|
||||
return &mocks.RolesStore{
|
||||
GetF: func(ctx context.Context, name string) (*chronograf.Role, error) {
|
||||
return &chronograf.Role{
|
||||
Name: "biffsgang",
|
||||
Permissions: chronograf.Permissions{
|
||||
{
|
||||
Name: "grays_sports_almanac",
|
||||
Scope: "DBScope",
|
||||
Allowed: chronograf.Allowances{
|
||||
"ReadData",
|
||||
},
|
||||
},
|
||||
},
|
||||
Users: []chronograf.User{
|
||||
{
|
||||
Name: "match",
|
||||
},
|
||||
{
|
||||
Name: "skinhead",
|
||||
},
|
||||
{
|
||||
Name: "3-d",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
ID: "1",
|
||||
RoleID: "biffsgang",
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"users":[{"name":"match","links":{"self":"/chronograf/v1/sources/1/users/match"}},{"name":"skinhead","links":{"self":"/chronograf/v1/sources/1/users/skinhead"}},{"name":"3-d","links":{"self":"/chronograf/v1/sources/1/users/3-d"}}],"name":"biffsgang","permissions":[{"scope":"DBScope","name":"grays_sports_almanac","allowed":["ReadData"]}],"links":{"self":"/chronograf/v1/sources/1/roles/biffsgang"}}
|
||||
`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
h := &server.Service{
|
||||
SourcesStore: tt.fields.SourcesStore,
|
||||
TimeSeries: tt.fields.TimeSeries,
|
||||
Logger: tt.fields.Logger,
|
||||
}
|
||||
|
||||
tt.args.r = tt.args.r.WithContext(httprouter.WithParams(
|
||||
context.Background(),
|
||||
httprouter.Params{
|
||||
{
|
||||
Key: "id",
|
||||
Value: tt.ID,
|
||||
},
|
||||
{
|
||||
Key: "rid",
|
||||
Value: tt.RoleID,
|
||||
},
|
||||
}))
|
||||
|
||||
h.RoleID(tt.args.w, tt.args.r)
|
||||
|
||||
resp := tt.args.w.Result()
|
||||
content := resp.Header.Get("Content-Type")
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != tt.wantStatus {
|
||||
t.Errorf("%q. RoleID() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus)
|
||||
}
|
||||
if tt.wantContentType != "" && content != tt.wantContentType {
|
||||
t.Errorf("%q. RoleID() = %v, want %v", tt.name, content, tt.wantContentType)
|
||||
}
|
||||
if tt.wantBody != "" && string(body) != tt.wantBody {
|
||||
t.Errorf("%q. RoleID() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_RemoveRole(t *testing.T) {
|
||||
type fields struct {
|
||||
SourcesStore chronograf.SourcesStore
|
||||
TimeSeries chronograf.TimeSeries
|
||||
Logger chronograf.Logger
|
||||
}
|
||||
type args struct {
|
||||
w *httptest.ResponseRecorder
|
||||
r *http.Request
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
ID string
|
||||
RoleID string
|
||||
wantStatus int
|
||||
}{
|
||||
{
|
||||
name: "remove role for data source",
|
||||
args: args{
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest(
|
||||
"GET",
|
||||
"http://server.local/chronograf/v1/sources/1/roles/biffsgang",
|
||||
nil),
|
||||
},
|
||||
fields: fields{
|
||||
Logger: log.New(log.DebugLevel),
|
||||
SourcesStore: &mocks.SourcesStore{
|
||||
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
|
||||
return chronograf.Source{
|
||||
ID: 1,
|
||||
Name: "muh source",
|
||||
Username: "name",
|
||||
Password: "hunter2",
|
||||
URL: "http://localhost:8086",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
TimeSeries: &mocks.TimeSeries{
|
||||
ConnectF: func(ctx context.Context, src *chronograf.Source) error {
|
||||
return nil
|
||||
},
|
||||
RolesF: func(ctx context.Context) (chronograf.RolesStore, error) {
|
||||
return &mocks.RolesStore{
|
||||
DeleteF: func(context.Context, *chronograf.Role) error {
|
||||
return nil
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
ID: "1",
|
||||
RoleID: "biffsgang",
|
||||
wantStatus: http.StatusNoContent,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
h := &server.Service{
|
||||
SourcesStore: tt.fields.SourcesStore,
|
||||
TimeSeries: tt.fields.TimeSeries,
|
||||
Logger: tt.fields.Logger,
|
||||
}
|
||||
|
||||
tt.args.r = tt.args.r.WithContext(httprouter.WithParams(
|
||||
context.Background(),
|
||||
httprouter.Params{
|
||||
{
|
||||
Key: "id",
|
||||
Value: tt.ID,
|
||||
},
|
||||
{
|
||||
Key: "rid",
|
||||
Value: tt.RoleID,
|
||||
},
|
||||
}))
|
||||
|
||||
h.RemoveRole(tt.args.w, tt.args.r)
|
||||
|
||||
resp := tt.args.w.Result()
|
||||
if resp.StatusCode != tt.wantStatus {
|
||||
t.Errorf("%q. RemoveRole() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_Roles(t *testing.T) {
|
||||
type fields struct {
|
||||
SourcesStore chronograf.SourcesStore
|
||||
TimeSeries chronograf.TimeSeries
|
||||
Logger chronograf.Logger
|
||||
}
|
||||
type args struct {
|
||||
w *httptest.ResponseRecorder
|
||||
r *http.Request
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
ID string
|
||||
RoleID string
|
||||
wantStatus int
|
||||
wantContentType string
|
||||
wantBody string
|
||||
}{
|
||||
{
|
||||
name: "Get roles for data source",
|
||||
args: args{
|
||||
w: httptest.NewRecorder(),
|
||||
r: httptest.NewRequest(
|
||||
"GET",
|
||||
"http://server.local/chronograf/v1/sources/1/roles",
|
||||
nil),
|
||||
},
|
||||
fields: fields{
|
||||
Logger: log.New(log.DebugLevel),
|
||||
SourcesStore: &mocks.SourcesStore{
|
||||
GetF: func(ctx context.Context, ID int) (chronograf.Source, error) {
|
||||
return chronograf.Source{
|
||||
ID: 1,
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
TimeSeries: &mocks.TimeSeries{
|
||||
ConnectF: func(ctx context.Context, src *chronograf.Source) error {
|
||||
return nil
|
||||
},
|
||||
RolesF: func(ctx context.Context) (chronograf.RolesStore, error) {
|
||||
return &mocks.RolesStore{
|
||||
AllF: func(ctx context.Context) ([]chronograf.Role, error) {
|
||||
return []chronograf.Role{
|
||||
chronograf.Role{
|
||||
Name: "biffsgang",
|
||||
Permissions: chronograf.Permissions{
|
||||
{
|
||||
Name: "grays_sports_almanac",
|
||||
Scope: "DBScope",
|
||||
Allowed: chronograf.Allowances{
|
||||
"ReadData",
|
||||
},
|
||||
},
|
||||
},
|
||||
Users: []chronograf.User{
|
||||
{
|
||||
Name: "match",
|
||||
},
|
||||
{
|
||||
Name: "skinhead",
|
||||
},
|
||||
{
|
||||
Name: "3-d",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
ID: "1",
|
||||
RoleID: "biffsgang",
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"roles":[{"users":[{"name":"match","links":{"self":"/chronograf/v1/sources/1/users/match"}},{"name":"skinhead","links":{"self":"/chronograf/v1/sources/1/users/skinhead"}},{"name":"3-d","links":{"self":"/chronograf/v1/sources/1/users/3-d"}}],"name":"biffsgang","permissions":[{"scope":"DBScope","name":"grays_sports_almanac","allowed":["ReadData"]}],"links":{"self":"/chronograf/v1/sources/1/roles/biffsgang"}}]}
|
||||
`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
h := &server.Service{
|
||||
SourcesStore: tt.fields.SourcesStore,
|
||||
TimeSeries: tt.fields.TimeSeries,
|
||||
Logger: tt.fields.Logger,
|
||||
}
|
||||
|
||||
tt.args.r = tt.args.r.WithContext(httprouter.WithParams(
|
||||
context.Background(),
|
||||
httprouter.Params{
|
||||
{
|
||||
Key: "id",
|
||||
Value: tt.ID,
|
||||
},
|
||||
{
|
||||
Key: "rid",
|
||||
Value: tt.RoleID,
|
||||
},
|
||||
}))
|
||||
|
||||
h.Roles(tt.args.w, tt.args.r)
|
||||
|
||||
resp := tt.args.w.Result()
|
||||
content := resp.Header.Get("Content-Type")
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != tt.wantStatus {
|
||||
t.Errorf("%q. RoleID() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus)
|
||||
}
|
||||
if tt.wantContentType != "" && content != tt.wantContentType {
|
||||
t.Errorf("%q. RoleID() = %v, want %v", tt.name, content, tt.wantContentType)
|
||||
}
|
||||
if tt.wantBody != "" && string(body) != tt.wantBody {
|
||||
t.Errorf("%q. RoleID() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,11 +11,12 @@ import (
|
|||
)
|
||||
|
||||
type sourceLinks struct {
|
||||
Self string `json:"self"` // Self link mapping to this resource
|
||||
Kapacitors string `json:"kapacitors"` // URL for kapacitors endpoint
|
||||
Proxy string `json:"proxy"` // URL for proxy endpoint
|
||||
Permissions string `json:"permissions"` // URL for all allowed permissions for this source
|
||||
Users string `json:"users"` // URL for all users associated with this source
|
||||
Self string `json:"self"` // Self link mapping to this resource
|
||||
Kapacitors string `json:"kapacitors"` // URL for kapacitors endpoint
|
||||
Proxy string `json:"proxy"` // URL for proxy endpoint
|
||||
Permissions string `json:"permissions"` // URL for all allowed permissions for this source
|
||||
Users string `json:"users"` // URL for all users associated with this source
|
||||
Roles string `json:"roles,omitempty"` // URL for all users associated with this source
|
||||
}
|
||||
|
||||
type sourceResponse struct {
|
||||
|
@ -33,7 +34,7 @@ func newSourceResponse(src chronograf.Source) sourceResponse {
|
|||
src.Password = ""
|
||||
|
||||
httpAPISrcs := "/chronograf/v1/sources"
|
||||
return sourceResponse{
|
||||
res := sourceResponse{
|
||||
Source: src,
|
||||
Links: sourceLinks{
|
||||
Self: fmt.Sprintf("%s/%d", httpAPISrcs, src.ID),
|
||||
|
@ -43,6 +44,11 @@ func newSourceResponse(src chronograf.Source) sourceResponse {
|
|||
Users: fmt.Sprintf("%s/%d/users", httpAPISrcs, src.ID),
|
||||
},
|
||||
}
|
||||
|
||||
if src.Type == "influx-enterprise" {
|
||||
res.Links.Roles = fmt.Sprintf("%s/%d/roles", httpAPISrcs, src.ID)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// NewSource adds a new valid source to the store
|
||||
|
|
|
@ -537,6 +537,238 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/sources/{id}/roles": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"sources",
|
||||
"users",
|
||||
"roles"
|
||||
],
|
||||
"summary": "Retrieve all data sources roles. Available only in Influx Enterprise",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"type": "string",
|
||||
"description": "ID of the data source",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Listing of all roles",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Roles"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Data source id does not exist.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "A processing or an unexpected error.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"sources",
|
||||
"users",
|
||||
"roles"
|
||||
],
|
||||
"summary": "Create new role for this data source",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"type": "string",
|
||||
"description": "ID of the data source",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "roleuser",
|
||||
"in": "body",
|
||||
"description": "Configuration options for new role",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Role"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Successfully created new role",
|
||||
"headers": {
|
||||
"Location": {
|
||||
"type": "string",
|
||||
"format": "url",
|
||||
"description": "Location of the newly created role resource."
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Role"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Data source id does not exist.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "A processing or an unexpected error.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sources/{id}/roles/{role_id}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"sources",
|
||||
"users",
|
||||
"roles"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"type": "string",
|
||||
"description": "ID of the data source",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "role_id",
|
||||
"in": "path",
|
||||
"type": "string",
|
||||
"description": "ID of the specific role",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"summary": "Returns information about a specific role",
|
||||
"description": "Specific role within a data source",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Information relating to the role",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Role"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Unknown role or unknown source",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Unexpected internal service error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"tags": [
|
||||
"sources",
|
||||
"users",
|
||||
"roles"
|
||||
],
|
||||
"summary": "Update role configuration",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"type": "string",
|
||||
"description": "ID of the data source",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "role_id",
|
||||
"in": "path",
|
||||
"type": "string",
|
||||
"description": "ID of the specific role",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "config",
|
||||
"in": "body",
|
||||
"description": "role configuration",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Role"
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Roles's configuration was changed",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Role"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Happens when trying to access a non-existent role or source.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "A processing or an unexpected error.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"sources",
|
||||
"users",
|
||||
"roles"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"type": "string",
|
||||
"description": "ID of the data source",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "role_id",
|
||||
"in": "path",
|
||||
"type": "string",
|
||||
"description": "ID of the specific role",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"summary": "This specific role will be removed from the data source",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Role has been removed"
|
||||
},
|
||||
"404": {
|
||||
"description": "Unknown role id or data source",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Unexpected internal service error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sources/{id}/kapacitors": {
|
||||
"get": {
|
||||
"tags": [
|
||||
|
@ -1590,7 +1822,7 @@
|
|||
},
|
||||
"put": {
|
||||
"tags": [
|
||||
"layouts"
|
||||
"dashboards"
|
||||
],
|
||||
"summary": "Replace dashboard information.",
|
||||
"parameters": [
|
||||
|
@ -2102,7 +2334,8 @@
|
|||
"kapacitors": "/chronograf/v1/sources/4/kapacitors",
|
||||
"proxy": "/chronograf/v1/sources/4/proxy",
|
||||
"permissions": "/chronograf/v1/sources/4/permissions",
|
||||
"users": "/chronograf/v1/sources/4/users"
|
||||
"users": "/chronograf/v1/sources/4/users",
|
||||
"roles": "/chronograf/v1/sources/4/roles"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -2180,6 +2413,11 @@
|
|||
"type": "string",
|
||||
"description": "URL location of the permissions endpoint for this source",
|
||||
"format": "url"
|
||||
},
|
||||
"roles": {
|
||||
"type": "string",
|
||||
"description": "Optional path to the roles endpoint IFF it is supported on this source",
|
||||
"format": "url"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2268,6 +2506,48 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Roles": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Role"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Role": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Unique name of the role",
|
||||
"maxLength": 254,
|
||||
"minLength": 1
|
||||
},
|
||||
"users": {
|
||||
"$ref": "#/definitions/Users"
|
||||
},
|
||||
"permissions": {
|
||||
"$ref": "#/definitions/Permissions"
|
||||
},
|
||||
"links": {
|
||||
"type": "object",
|
||||
"description": "URL relations of this role",
|
||||
"properties": {
|
||||
"self": {
|
||||
"type": "string",
|
||||
"format": "url",
|
||||
"description": "URI of resource."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Users": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -54,7 +54,7 @@ func TestService_Me(t *testing.T) {
|
|||
principal: "me",
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"username":"me","password":"hunter2","links":{"self":"/chronograf/v1/users/me"}}
|
||||
wantBody: `{"name":"me","password":"hunter2","links":{"self":"/chronograf/v1/users/me"}}
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -77,7 +77,7 @@ func TestService_Me(t *testing.T) {
|
|||
principal: "secret",
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"username":"secret","password":"","links":{"self":"/chronograf/v1/users/secret"}}
|
||||
wantBody: `{"name":"secret","password":"","links":{"self":"/chronograf/v1/users/secret"}}
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue