Merge pull request #955 from influxdata/feature/users-with-roles
Update InfluxEnterprise users to return their rolespull/959/head
commit
71c2b03204
|
@ -275,6 +275,7 @@ type User struct {
|
|||
Name string `json:"name"`
|
||||
Passwd string `json:"password"`
|
||||
Permissions Permissions `json:"permissions,omitempty"`
|
||||
Roles []Role `json:"roles,omitempty"`
|
||||
}
|
||||
|
||||
// UsersStore is the Storage and retrieval of authentication information
|
||||
|
|
|
@ -24,6 +24,8 @@ type Ctrl interface {
|
|||
ChangePassword(ctx context.Context, name, passwd string) error
|
||||
SetUserPerms(ctx context.Context, name string, perms Permissions) error
|
||||
|
||||
UserRoles(ctx context.Context) (map[string]Roles, error)
|
||||
|
||||
Roles(ctx context.Context, name *string) (*Roles, error)
|
||||
Role(ctx context.Context, name string) (*Role, error)
|
||||
CreateRole(ctx context.Context, name string) error
|
||||
|
|
|
@ -155,6 +155,27 @@ func (m *MetaClient) SetUserPerms(ctx context.Context, name string, perms Permis
|
|||
return m.Post(ctx, "/user", a, nil)
|
||||
}
|
||||
|
||||
// UserRoles returns a map of users to all of their current roles
|
||||
func (m *MetaClient) UserRoles(ctx context.Context) (map[string]Roles, error) {
|
||||
res, err := m.Roles(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userRoles := make(map[string]Roles)
|
||||
for _, role := range res.Roles {
|
||||
for _, u := range role.Users {
|
||||
ur, ok := userRoles[u]
|
||||
if !ok {
|
||||
ur = Roles{}
|
||||
}
|
||||
ur.Roles = append(ur.Roles, role)
|
||||
userRoles[u] = ur
|
||||
}
|
||||
}
|
||||
return userRoles, nil
|
||||
}
|
||||
|
||||
// Roles gets all the roles. If name is not nil it filters for a single role
|
||||
func (m *MetaClient) Roles(ctx context.Context, name *string) (*Roles, error) {
|
||||
params := map[string]string{}
|
||||
|
|
|
@ -876,6 +876,110 @@ func TestMetaClient_Role(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMetaClient_UserRoles(t *testing.T) {
|
||||
type fields struct {
|
||||
URL *url.URL
|
||||
client interface {
|
||||
Do(URL *url.URL, path, method string, params map[string]string, body io.Reader) (*http.Response, error)
|
||||
}
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
name *string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want map[string]Roles
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Successful Show all roles",
|
||||
fields: fields{
|
||||
URL: &url.URL{
|
||||
Host: "twinpinesmall.net:8091",
|
||||
Scheme: "https",
|
||||
},
|
||||
client: NewMockClient(
|
||||
http.StatusOK,
|
||||
[]byte(`{"roles":[{"name":"timetravelers","users":["marty","docbrown"],"permissions":{"":["ViewAdmin","ViewChronograf"]}},{"name":"mcfly","users":["marty","george"],"permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`),
|
||||
nil,
|
||||
nil,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
name: nil,
|
||||
},
|
||||
want: map[string]Roles{
|
||||
"marty": Roles{
|
||||
Roles: []Role{
|
||||
{
|
||||
Name: "timetravelers",
|
||||
Permissions: map[string][]string{
|
||||
"": []string{
|
||||
"ViewAdmin", "ViewChronograf",
|
||||
},
|
||||
},
|
||||
Users: []string{"marty", "docbrown"},
|
||||
},
|
||||
{
|
||||
Name: "mcfly",
|
||||
Permissions: map[string][]string{
|
||||
"": []string{
|
||||
"ViewAdmin", "ViewChronograf",
|
||||
},
|
||||
},
|
||||
Users: []string{"marty", "george"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"docbrown": Roles{
|
||||
Roles: []Role{
|
||||
{
|
||||
Name: "timetravelers",
|
||||
Permissions: map[string][]string{
|
||||
"": []string{
|
||||
"ViewAdmin", "ViewChronograf",
|
||||
},
|
||||
},
|
||||
Users: []string{"marty", "docbrown"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"george": Roles{
|
||||
Roles: []Role{
|
||||
{
|
||||
Name: "mcfly",
|
||||
Permissions: map[string][]string{
|
||||
"": []string{
|
||||
"ViewAdmin", "ViewChronograf",
|
||||
},
|
||||
},
|
||||
Users: []string{"marty", "george"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
m := &MetaClient{
|
||||
URL: tt.fields.URL,
|
||||
client: tt.fields.client,
|
||||
}
|
||||
got, err := m.UserRoles(tt.args.ctx)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("%q. MetaClient.UserRoles() error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("%q. MetaClient.UserRoles() = %v, want %v", tt.name, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetaClient_CreateRole(t *testing.T) {
|
||||
type fields struct {
|
||||
URL *url.URL
|
||||
|
|
|
@ -68,6 +68,10 @@ func (cc *ControlClient) Role(ctx context.Context, name string) (*enterprise.Rol
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (ccm *ControlClient) UserRoles(ctx context.Context) (map[string]enterprise.Roles, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (ccm *ControlClient) Roles(ctx context.Context, name *string) (*enterprise.Roles, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -85,9 +85,13 @@ func (c *RolesStore) All(ctx context.Context) ([]chronograf.Role, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
res := make([]chronograf.Role, len(all.Roles))
|
||||
for i, role := range all.Roles {
|
||||
return all.ToChronograf(), nil
|
||||
}
|
||||
|
||||
// ToChronograf converts enterprise roles to chronograf
|
||||
func (r *Roles) ToChronograf() []chronograf.Role {
|
||||
res := make([]chronograf.Role, len(r.Roles))
|
||||
for i, role := range r.Roles {
|
||||
users := make([]chronograf.User, len(role.Users))
|
||||
for i, user := range role.Users {
|
||||
users[i] = chronograf.User{
|
||||
|
@ -101,5 +105,5 @@ func (c *RolesStore) All(ctx context.Context) ([]chronograf.Role, error) {
|
|||
Users: users,
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
return res
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package enterprise
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
func TestRoles_ToChronograf(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
roles []Role
|
||||
want []chronograf.Role
|
||||
}{
|
||||
{
|
||||
name: "empty roles",
|
||||
roles: []Role{},
|
||||
want: []chronograf.Role{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Roles{
|
||||
Roles: tt.roles,
|
||||
}
|
||||
if got := r.ToChronograf(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Roles.ToChronograf() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -35,9 +35,23 @@ func (c *UserStore) Get(ctx context.Context, name string) (*chronograf.User, err
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ur, err := c.Ctrl.UserRoles(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
role := ur[name]
|
||||
cr := role.ToChronograf()
|
||||
// For now we are removing all users from a role being returned.
|
||||
for i, r := range cr {
|
||||
r.Users = []chronograf.User{}
|
||||
cr[i] = r
|
||||
}
|
||||
return &chronograf.User{
|
||||
Name: u.Name,
|
||||
Permissions: ToChronograf(u.Permissions),
|
||||
Roles: cr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -59,11 +73,25 @@ func (c *UserStore) All(ctx context.Context) ([]chronograf.User, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ur, err := c.Ctrl.UserRoles(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := make([]chronograf.User, len(all.Users))
|
||||
for i, user := range all.Users {
|
||||
role := ur[user.Name]
|
||||
cr := role.ToChronograf()
|
||||
// For now we are removing all users from a role being returned.
|
||||
for i, r := range cr {
|
||||
r.Users = []chronograf.User{}
|
||||
cr[i] = r
|
||||
}
|
||||
|
||||
res[i] = chronograf.User{
|
||||
Name: user.Name,
|
||||
Permissions: ToChronograf(user.Permissions),
|
||||
Roles: cr,
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
|
|
|
@ -180,6 +180,9 @@ func TestClient_Get(t *testing.T) {
|
|||
},
|
||||
}, nil
|
||||
},
|
||||
userRoles: func(ctx context.Context) (map[string]enterprise.Roles, error) {
|
||||
return map[string]enterprise.Roles{}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
|
@ -194,6 +197,71 @@ func TestClient_Get(t *testing.T) {
|
|||
Allowed: chronograf.Allowances{"ViewChronograf", "ReadData", "WriteData"},
|
||||
},
|
||||
},
|
||||
Roles: []chronograf.Role{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Successful Get User with roles",
|
||||
fields: fields{
|
||||
Ctrl: &mockCtrl{
|
||||
user: func(ctx context.Context, name string) (*enterprise.User, error) {
|
||||
return &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{
|
||||
"marty": enterprise.Roles{
|
||||
Roles: []enterprise.Role{
|
||||
{
|
||||
Name: "timetravels",
|
||||
Permissions: map[string][]string{
|
||||
"": {
|
||||
"ViewChronograf",
|
||||
"ReadData",
|
||||
"WriteData",
|
||||
},
|
||||
},
|
||||
Users: []string{"marty", "docbrown"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
name: "marty",
|
||||
},
|
||||
want: &chronograf.User{
|
||||
Name: "marty",
|
||||
Permissions: chronograf.Permissions{
|
||||
{
|
||||
Scope: chronograf.AllScope,
|
||||
Allowed: chronograf.Allowances{"ViewChronograf", "ReadData", "WriteData"},
|
||||
},
|
||||
},
|
||||
Roles: []chronograf.Role{
|
||||
{
|
||||
Name: "timetravels",
|
||||
Permissions: chronograf.Permissions{
|
||||
{
|
||||
Scope: chronograf.AllScope,
|
||||
Allowed: chronograf.Allowances{"ViewChronograf", "ReadData", "WriteData"},
|
||||
},
|
||||
},
|
||||
Users: []chronograf.User{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -372,6 +440,9 @@ func TestClient_All(t *testing.T) {
|
|||
},
|
||||
}, nil
|
||||
},
|
||||
userRoles: func(ctx context.Context) (map[string]enterprise.Roles, error) {
|
||||
return map[string]enterprise.Roles{}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
|
@ -386,6 +457,7 @@ func TestClient_All(t *testing.T) {
|
|||
Allowed: chronograf.Allowances{"ViewChronograf", "ReadData", "WriteData"},
|
||||
},
|
||||
},
|
||||
Roles: []chronograf.Role{},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -499,6 +571,8 @@ type mockCtrl struct {
|
|||
users func(ctx context.Context, name *string) (*enterprise.Users, error)
|
||||
setUserPerms func(ctx context.Context, name string, perms enterprise.Permissions) error
|
||||
|
||||
userRoles func(ctx context.Context) (map[string]enterprise.Roles, 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
|
||||
|
@ -510,25 +584,35 @@ type mockCtrl struct {
|
|||
func (m *mockCtrl) ShowCluster(ctx context.Context) (*enterprise.Cluster, error) {
|
||||
return m.showCluster(ctx)
|
||||
}
|
||||
|
||||
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 {
|
||||
return m.createUser(ctx, name, passwd)
|
||||
}
|
||||
|
||||
func (m *mockCtrl) DeleteUser(ctx context.Context, name string) error {
|
||||
return m.deleteUser(ctx, name)
|
||||
}
|
||||
|
||||
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) (*enterprise.Users, error) {
|
||||
return m.users(ctx, name)
|
||||
}
|
||||
|
||||
func (m *mockCtrl) SetUserPerms(ctx context.Context, name string, perms enterprise.Permissions) error {
|
||||
return m.setUserPerms(ctx, name, perms)
|
||||
}
|
||||
|
||||
func (m *mockCtrl) UserRoles(ctx context.Context) (map[string]enterprise.Roles, error) {
|
||||
return m.userRoles(ctx)
|
||||
}
|
||||
|
||||
func (m *mockCtrl) Roles(ctx context.Context, name *string) (*enterprise.Roles, error) {
|
||||
return m.roles(ctx, name)
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ func (r *sourceUserRequest) ValidUpdate() error {
|
|||
type sourceUser struct {
|
||||
Username string `json:"name,omitempty"` // Username for new account
|
||||
Permissions chronograf.Permissions `json:"permissions,omitempty"` // Account's permissions
|
||||
Roles []roleResponse `json:"roles,omitempty"` // Roles if source uses them
|
||||
Links selfLinks `json:"links"` // Links are URI locations related to user
|
||||
}
|
||||
|
||||
|
@ -128,11 +129,19 @@ func (h *Service) SourceUsers(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
su := []sourceUser{}
|
||||
for _, u := range users {
|
||||
su = append(su, sourceUser{
|
||||
res := sourceUser{
|
||||
Username: u.Name,
|
||||
Permissions: u.Permissions,
|
||||
Links: newSelfLinks(srcID, "users", u.Name),
|
||||
})
|
||||
}
|
||||
if len(u.Roles) > 0 {
|
||||
rr := make([]roleResponse, len(u.Roles))
|
||||
for i, role := range u.Roles {
|
||||
rr[i] = newRoleResponse(srcID, &role)
|
||||
}
|
||||
res.Roles = rr
|
||||
}
|
||||
su = append(su, res)
|
||||
}
|
||||
|
||||
res := sourceUsers{
|
||||
|
@ -163,6 +172,13 @@ func (h *Service) SourceUserID(w http.ResponseWriter, r *http.Request) {
|
|||
Permissions: u.Permissions,
|
||||
Links: newSelfLinks(srcID, "users", u.Name),
|
||||
}
|
||||
if len(u.Roles) > 0 {
|
||||
rr := make([]roleResponse, len(u.Roles))
|
||||
for i, role := range u.Roles {
|
||||
rr[i] = newRoleResponse(srcID, &role)
|
||||
}
|
||||
res.Roles = rr
|
||||
}
|
||||
encodeJSON(w, http.StatusOK, res, h.Logger)
|
||||
}
|
||||
|
||||
|
@ -346,7 +362,7 @@ func (r *sourceRoleRequest) ValidUpdate() error {
|
|||
}
|
||||
|
||||
type roleResponse struct {
|
||||
Users []sourceUser `json:"users"`
|
||||
Users []sourceUser `json:"users,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Permissions chronograf.Permissions `json:"permissions"`
|
||||
Links selfLinks `json:"links"`
|
||||
|
|
|
@ -2565,6 +2565,34 @@
|
|||
"$ref": "#/definitions/Role"
|
||||
}
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"roles": [
|
||||
{
|
||||
"users": [
|
||||
{
|
||||
"name": "admin",
|
||||
"links": {
|
||||
"self": "/chronograf/v1/sources/3/users/admin"
|
||||
}
|
||||
}
|
||||
],
|
||||
"name": "timetravelers",
|
||||
"permissions": [
|
||||
{
|
||||
"scope": "database",
|
||||
"name": "telegraf",
|
||||
"allowed": [
|
||||
"ReadData",
|
||||
"WriteData"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "/chronograf/v1/sources/3/roles/timetravelers"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Role": {
|
||||
|
@ -2596,6 +2624,30 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"users": [
|
||||
{
|
||||
"name": "admin",
|
||||
"links": {
|
||||
"self": "/chronograf/v1/sources/3/users/admin"
|
||||
}
|
||||
}
|
||||
],
|
||||
"name": "timetravelers",
|
||||
"permissions": [
|
||||
{
|
||||
"scope": "database",
|
||||
"name": "telegraf",
|
||||
"allowed": [
|
||||
"ReadData",
|
||||
"WriteData"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "/chronograf/v1/sources/3/roles/timetravelers"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Users": {
|
||||
|
@ -2616,21 +2668,43 @@
|
|||
{
|
||||
"scope": "all",
|
||||
"allowed": [
|
||||
"ViewAdmin",
|
||||
"ViewChronograf",
|
||||
"ReadData"
|
||||
]
|
||||
},
|
||||
{
|
||||
"scope": "database",
|
||||
"name": "telegraf",
|
||||
"allowed": [
|
||||
"ViewChronograf",
|
||||
"ReadData"
|
||||
"CreateDatabase",
|
||||
"CreateUserAndRole",
|
||||
"DropDatabase",
|
||||
"DropData",
|
||||
"ReadData",
|
||||
"WriteData",
|
||||
"ManageShard",
|
||||
"ManageContinuousQuery",
|
||||
"ManageQuery",
|
||||
"ManageSubscription",
|
||||
"Monitor",
|
||||
"KapacitorAPI"
|
||||
]
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
{
|
||||
"name": "timetravelers",
|
||||
"permissions": [
|
||||
{
|
||||
"scope": "database",
|
||||
"name": "telegraf",
|
||||
"allowed": [
|
||||
"ReadData",
|
||||
"WriteData"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "/chronograf/v1/sources/3/roles/timetravelers"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "/chronograf/v1/source/1/users/docbrown"
|
||||
"self": "/chronograf/v1/sources/3/users/docbrown"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -2669,21 +2743,43 @@
|
|||
{
|
||||
"scope": "all",
|
||||
"allowed": [
|
||||
"ViewAdmin",
|
||||
"ViewChronograf",
|
||||
"ReadData"
|
||||
]
|
||||
},
|
||||
{
|
||||
"scope": "database",
|
||||
"name": "telegraf",
|
||||
"allowed": [
|
||||
"ViewChronograf",
|
||||
"ReadData"
|
||||
"CreateDatabase",
|
||||
"CreateUserAndRole",
|
||||
"DropDatabase",
|
||||
"DropData",
|
||||
"ReadData",
|
||||
"WriteData",
|
||||
"ManageShard",
|
||||
"ManageContinuousQuery",
|
||||
"ManageQuery",
|
||||
"ManageSubscription",
|
||||
"Monitor",
|
||||
"KapacitorAPI"
|
||||
]
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
{
|
||||
"name": "timetravelers",
|
||||
"permissions": [
|
||||
{
|
||||
"scope": "database",
|
||||
"name": "telegraf",
|
||||
"allowed": [
|
||||
"ReadData",
|
||||
"WriteData"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "/chronograf/v1/sources/3/roles/timetravelers"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "/chronograf/v1/source/1/users/docbrown"
|
||||
"self": "/chronograf/v1/sources/3/users/docbrown"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue