Merge pull request #2132 from influxdata/multitenancy_authorize_role

Role based authorization
pull/2160/head
Jared Scheib 2017-10-19 14:55:01 -05:00 committed by GitHub
commit 4b445b10d1
17 changed files with 1003 additions and 150 deletions

View File

@ -2,7 +2,7 @@ package bolt
import (
"context"
"strconv"
"fmt"
"github.com/boltdb/bolt"
"github.com/influxdata/chronograf"
@ -38,17 +38,48 @@ func (s *UsersStore) get(ctx context.Context, id uint64) (*chronograf.User, erro
return &u, nil
}
func (s *UsersStore) each(ctx context.Context, fn func(*chronograf.User)) error {
return s.client.db.View(func(tx *bolt.Tx) error {
return tx.Bucket(UsersBucket).ForEach(func(k, v []byte) error {
var user chronograf.User
if err := internal.UnmarshalUser(v, &user); err != nil {
return err
}
fn(&user)
return nil
})
})
}
// Get searches the UsersStore for user with name
func (s *UsersStore) Get(ctx context.Context, id string) (*chronograf.User, error) {
uid, err := strconv.ParseUint(id, 10, 64)
if err != nil {
return nil, err
func (s *UsersStore) Get(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.ID != nil {
return s.get(ctx, *q.ID)
}
u, err := s.get(ctx, uid)
if err != nil {
return nil, err
if q.Name != nil && q.Provider != nil && q.Scheme != nil {
var user *chronograf.User
err := s.each(ctx, func(u *chronograf.User) {
if user != nil {
return
}
if u.Name == *q.Name && u.Provider == *q.Provider && u.Scheme == *q.Scheme {
user = u
}
})
if err != nil {
return nil, err
}
if user == nil {
return nil, chronograf.ErrUserNotFound
}
return user, nil
}
return u, nil
return nil, fmt.Errorf("must specify either ID, or Name, Provider, and Scheme in UserQuery")
}
// Add a new Users in the UsersStore.

View File

@ -2,8 +2,6 @@ package bolt_test
import (
"context"
"fmt"
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
@ -18,25 +16,45 @@ var cmpOptions = cmp.Options{
cmpopts.EquateEmpty(),
}
func TestUsersStore_Get(t *testing.T) {
func TestUsersStore_GetWithID(t *testing.T) {
type args struct {
ctx context.Context
ID string
usr *chronograf.User
}
tests := []struct {
name string
args args
want *chronograf.User
wantErr bool
name string
args args
want *chronograf.User
wantErr bool
addFirst bool
}{
{
name: "User not found",
args: args{
ctx: context.Background(),
ID: "1337",
usr: &chronograf.User{
ID: 1337,
},
},
wantErr: true,
},
{
name: "Get user",
args: args{
ctx: context.Background(),
usr: &chronograf.User{
Name: "billietta",
Provider: "Google",
Scheme: "OAuth2",
},
},
want: &chronograf.User{
Name: "billietta",
Provider: "Google",
Scheme: "OAuth2",
},
addFirst: true,
},
}
for _, tt := range tests {
client, err := NewTestClient()
@ -49,17 +67,118 @@ func TestUsersStore_Get(t *testing.T) {
defer client.Close()
s := client.UsersStore
got, err := s.Get(tt.args.ctx, tt.args.ID)
if tt.addFirst {
tt.args.usr, err = s.Add(tt.args.ctx, tt.args.usr)
if err != nil {
t.Fatal(err)
}
}
got, err := s.Get(tt.args.ctx, chronograf.UserQuery{ID: &tt.args.usr.ID})
if (err != nil) != tt.wantErr {
t.Errorf("%q. UsersStore.Get() error = %v, wantErr %v", tt.name, err, tt.wantErr)
continue
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("%q. UsersStore.Get() = %v, want %v", tt.name, got, tt.want)
if diff := cmp.Diff(got, tt.want, cmpOptions...); diff != "" {
t.Errorf("%q. UsersStore.Get():\n-got/+want\ndiff %s", tt.name, diff)
}
}
}
func TestUsersStore_GetWithNameProviderScheme(t *testing.T) {
type args struct {
ctx context.Context
name string
provider string
usr *chronograf.User
}
tests := []struct {
name string
args args
want *chronograf.User
wantErr bool
addFirst bool
}{
{
name: "User not found",
args: args{
ctx: context.Background(),
usr: &chronograf.User{
Name: "billietta",
Provider: "Google",
Scheme: "OAuth2",
},
},
wantErr: true,
},
{
name: "Get user",
args: args{
ctx: context.Background(),
usr: &chronograf.User{
Name: "billietta",
Provider: "Google",
Scheme: "OAuth2",
},
},
want: &chronograf.User{
Name: "billietta",
Provider: "Google",
Scheme: "OAuth2",
},
addFirst: true,
},
}
for _, tt := range tests {
client, err := NewTestClient()
if err != nil {
t.Fatal(err)
}
if err := client.Open(context.TODO()); err != nil {
t.Fatal(err)
}
defer client.Close()
s := client.UsersStore
if tt.addFirst {
tt.args.usr, err = s.Add(tt.args.ctx, tt.args.usr)
if err != nil {
t.Fatal(err)
}
}
got, err := s.Get(tt.args.ctx, chronograf.UserQuery{
Name: &tt.args.usr.Name,
Provider: &tt.args.usr.Provider,
Scheme: &tt.args.usr.Scheme,
})
if (err != nil) != tt.wantErr {
t.Errorf("%q. UsersStore.Get() error = %v, wantErr %v", tt.name, err, tt.wantErr)
continue
}
if diff := cmp.Diff(got, tt.want, cmpOptions...); diff != "" {
t.Errorf("%q. UsersStore.Get():\n-got/+want\ndiff %s", tt.name, diff)
}
}
}
func TestUsersStore_GetInvalid(t *testing.T) {
client, err := NewTestClient()
if err != nil {
t.Fatal(err)
}
if err := client.Open(context.TODO()); err != nil {
t.Fatal(err)
}
defer client.Close()
s := client.UsersStore
_, err = s.Get(context.Background(), chronograf.UserQuery{})
if err == nil {
t.Errorf("Invalid Get. UsersStore.Get() error = %v", err)
}
}
func TestUsersStore_Add(t *testing.T) {
type args struct {
ctx context.Context
@ -114,7 +233,7 @@ func TestUsersStore_Add(t *testing.T) {
continue
}
got, err = s.Get(tt.args.ctx, fmt.Sprintf("%d", got.ID))
got, err = s.Get(tt.args.ctx, chronograf.UserQuery{ID: &got.ID})
if err != nil {
t.Fatalf("failed to get user: %v", err)
}
@ -298,7 +417,7 @@ func TestUsersStore_Update(t *testing.T) {
continue
}
got, err := s.Get(tt.args.ctx, fmt.Sprintf("%d", tt.args.usr.ID))
got, err := s.Get(tt.args.ctx, chronograf.UserQuery{ID: &tt.args.usr.ID})
if err != nil {
t.Fatalf("failed to get user: %v", err)
}

View File

@ -606,6 +606,15 @@ type User struct {
Scheme string `json:"scheme,omitempty"`
}
// UserQuery represents the attributes that a user may be retrieved by.
// It is predominantly used in the UsersStore.Get method.
type UserQuery struct {
ID *uint64
Name *string
Provider *string
Scheme *string
}
// UsersStore is the Storage and retrieval of authentication information
type UsersStore interface {
// All lists all users from the UsersStore
@ -615,7 +624,7 @@ type UsersStore interface {
// Delete the User from the UsersStore
Delete(context.Context, *User) error
// Get retrieves a user if name exists.
Get(ctx context.Context, name string) (*User, error)
Get(ctx context.Context, q UserQuery) (*User, error)
// Update the user's permissions or roles
Update(context.Context, *User) error
}

View File

@ -2,6 +2,7 @@ package enterprise
import (
"context"
"fmt"
"github.com/influxdata/chronograf"
)
@ -28,7 +29,7 @@ func (c *UserStore) Add(ctx context.Context, u *chronograf.User) (*chronograf.Us
}
}
return c.Get(ctx, u.Name)
return c.Get(ctx, chronograf.UserQuery{Name: &u.Name})
}
// Delete the User from Influx Enterprise
@ -37,8 +38,11 @@ func (c *UserStore) Delete(ctx context.Context, u *chronograf.User) error {
}
// Get retrieves a user if name exists.
func (c *UserStore) Get(ctx context.Context, name string) (*chronograf.User, error) {
u, err := c.Ctrl.User(ctx, name)
func (c *UserStore) Get(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil {
return nil, fmt.Errorf("query must specify name")
}
u, err := c.Ctrl.User(ctx, *q.Name)
if err != nil {
return nil, err
}
@ -48,7 +52,7 @@ func (c *UserStore) Get(ctx context.Context, name string) (*chronograf.User, err
return nil, err
}
role := ur[name]
role := ur[*q.Name]
cr := role.ToChronograf()
// For now we are removing all users from a role being returned.
for i, r := range cr {

View File

@ -375,7 +375,7 @@ func TestClient_Get(t *testing.T) {
Ctrl: tt.fields.Ctrl,
Logger: tt.fields.Logger,
}
got, err := c.Get(tt.args.ctx, tt.args.name)
got, err := c.Get(tt.args.ctx, chronograf.UserQuery{Name: &tt.args.name})
if (err != nil) != tt.wantErr {
t.Errorf("%q. Client.Get() error = %v, wantErr %v", tt.name, err, tt.wantErr)
continue

View File

@ -21,7 +21,7 @@ func (c *Client) Add(ctx context.Context, u *chronograf.User) (*chronograf.User,
return nil, err
}
}
return c.Get(ctx, u.Name)
return c.Get(ctx, chronograf.UserQuery{Name: &u.Name})
}
// Delete the User from InfluxDB
@ -54,14 +54,18 @@ func (c *Client) Delete(ctx context.Context, u *chronograf.User) error {
}
// Get retrieves a user if name exists.
func (c *Client) Get(ctx context.Context, name string) (*chronograf.User, error) {
func (c *Client) Get(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil {
return nil, fmt.Errorf("query must specify name")
}
users, err := c.showUsers(ctx)
if err != nil {
return nil, err
}
for _, user := range users {
if user.Name == name {
if user.Name == *q.Name {
perms, err := c.userPermissions(ctx, user.Name)
if err != nil {
return nil, err
@ -82,7 +86,7 @@ func (c *Client) Update(ctx context.Context, u *chronograf.User) error {
return c.updatePassword(ctx, u.Name, u.Passwd)
}
user, err := c.Get(ctx, u.Name)
user, err := c.Get(ctx, chronograf.UserQuery{Name: &u.Name})
if err != nil {
return err
}

View File

@ -392,7 +392,7 @@ func TestClient_Get(t *testing.T) {
Logger: log.New(log.DebugLevel),
}
defer ts.Close()
got, err := c.Get(tt.args.ctx, tt.args.name)
got, err := c.Get(tt.args.ctx, chronograf.UserQuery{Name: &tt.args.name})
if (err != nil) != tt.wantErr {
t.Errorf("%q. Client.Get() error = %v, wantErr %v", tt.name, err, tt.wantErr)
continue

View File

@ -13,7 +13,7 @@ type UsersStore struct {
AllF func(context.Context) ([]chronograf.User, error)
AddF func(context.Context, *chronograf.User) (*chronograf.User, error)
DeleteF func(context.Context, *chronograf.User) error
GetF func(ctx context.Context, name string) (*chronograf.User, error)
GetF func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error)
UpdateF func(context.Context, *chronograf.User) error
}
@ -33,8 +33,8 @@ func (s *UsersStore) Delete(ctx context.Context, u *chronograf.User) error {
}
// Get retrieves a user if name exists.
func (s *UsersStore) Get(ctx context.Context, name string) (*chronograf.User, error) {
return s.GetF(ctx, name)
func (s *UsersStore) Get(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
return s.GetF(ctx, q)
}
// Update the user's permissions or roles

View File

@ -15,7 +15,7 @@ import (
func AuthorizedToken(auth oauth2.Authenticator, logger chronograf.Logger, next http.Handler) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log := logger.
WithField("component", "auth").
WithField("component", "token_auth").
WithField("remote_addr", r.RemoteAddr).
WithField("method", r.Method).
WithField("url", r.URL)
@ -45,3 +45,101 @@ func AuthorizedToken(auth oauth2.Authenticator, logger chronograf.Logger, next h
return
})
}
// AuthorizedUser extracts the user name and provider from context. If the
// user and provider can be found on the context, we look up the user by their
// name and provider. If the user is found, we verify that the user has at at
// least the role supplied.
func AuthorizedUser(
store chronograf.UsersStore,
useAuth bool,
role string,
logger chronograf.Logger,
next http.HandlerFunc,
) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !useAuth {
next(w, r)
return
}
log := logger.
WithField("component", "role_auth").
WithField("remote_addr", r.RemoteAddr).
WithField("method", r.Method).
WithField("url", r.URL)
ctx := r.Context()
username, err := getUsername(ctx)
if err != nil {
log.Error("Failed to retrieve username from context")
Error(w, http.StatusUnauthorized, "User is not authorized", logger)
return
}
provider, err := getProvider(ctx)
if err != nil {
log.Error("Failed to retrieve provider from context")
Error(w, http.StatusUnauthorized, "User is not authorized", logger)
return
}
scheme, err := getScheme(ctx)
if err != nil {
log.Error("Failed to retrieve scheme from context")
Error(w, http.StatusUnauthorized, "User is not authorized", logger)
return
}
u, err := store.Get(ctx, chronograf.UserQuery{
Name: &username,
Provider: &provider,
Scheme: &scheme,
})
if err != nil {
log.Error("Failed to retrieve user")
Error(w, http.StatusUnauthorized, "User is not authorized", logger)
return
}
if hasAuthorizedRole(u, role) {
next(w, r)
return
}
Error(w, http.StatusUnauthorized, "User is not authorized", logger)
return
})
}
func hasAuthorizedRole(u *chronograf.User, role string) bool {
if u == nil {
return false
}
switch role {
case ViewerRoleName:
for _, r := range u.Roles {
switch r.Name {
case ViewerRoleName, EditorRoleName, AdminRoleName:
return true
}
}
case EditorRoleName:
for _, r := range u.Roles {
switch r.Name {
case EditorRoleName, AdminRoleName:
return true
}
}
case AdminRoleName:
for _, r := range u.Roles {
switch r.Name {
case AdminRoleName:
return true
}
}
}
return false
}

View File

@ -3,11 +3,14 @@ package server_test
import (
"context"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/influxdata/chronograf"
clog "github.com/influxdata/chronograf/log"
"github.com/influxdata/chronograf/mocks"
"github.com/influxdata/chronograf/oauth2"
"github.com/influxdata/chronograf/server"
)
@ -94,3 +97,496 @@ func TestAuthorizedToken(t *testing.T) {
}
}
}
func TestAuthorizedUser(t *testing.T) {
type fields struct {
UsersStore chronograf.UsersStore
Logger chronograf.Logger
}
type args struct {
username string
provider string
useAuth bool
role string
}
tests := []struct {
name string
fields fields
args args
authorized bool
}{
{
name: "Not using auth",
fields: fields{
UsersStore: &mocks.UsersStore{},
Logger: clog.New(clog.DebugLevel),
},
args: args{
useAuth: false,
},
authorized: true,
},
{
name: "User with viewer role is viewer authorized",
fields: fields{
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return &chronograf.User{
ID: 1337,
Name: "billysteve",
Provider: "Google",
Scheme: "OAuth2",
Roles: []chronograf.Role{
server.ViewerRole,
},
}, nil
},
},
Logger: clog.New(clog.DebugLevel),
},
args: args{
username: "billysteve",
provider: "Google",
role: "viewer",
useAuth: true,
},
authorized: true,
},
{
name: "User with editor role is viewer authorized",
fields: fields{
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return &chronograf.User{
ID: 1337,
Name: "billysteve",
Provider: "Google",
Scheme: "OAuth2",
Roles: []chronograf.Role{
server.EditorRole,
},
}, nil
},
},
Logger: clog.New(clog.DebugLevel),
},
args: args{
username: "billysteve",
provider: "Google",
role: "viewer",
useAuth: true,
},
authorized: true,
},
{
name: "User with admin role is viewer authorized",
fields: fields{
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return &chronograf.User{
ID: 1337,
Name: "billysteve",
Provider: "Google",
Scheme: "OAuth2",
Roles: []chronograf.Role{
server.AdminRole,
},
}, nil
},
},
Logger: clog.New(clog.DebugLevel),
},
args: args{
username: "billysteve",
provider: "Google",
role: "viewer",
useAuth: true,
},
authorized: true,
},
{
name: "User with viewer role is editor unauthorized",
fields: fields{
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return &chronograf.User{
ID: 1337,
Name: "billysteve",
Provider: "Google",
Scheme: "OAuth2",
Roles: []chronograf.Role{
server.ViewerRole,
},
}, nil
},
},
Logger: clog.New(clog.DebugLevel),
},
args: args{
username: "billysteve",
provider: "Google",
role: "editor",
useAuth: true,
},
authorized: false,
},
{
name: "User with editor role is editor authorized",
fields: fields{
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return &chronograf.User{
ID: 1337,
Name: "billysteve",
Provider: "Google",
Scheme: "OAuth2",
Roles: []chronograf.Role{
server.EditorRole,
},
}, nil
},
},
Logger: clog.New(clog.DebugLevel),
},
args: args{
username: "billysteve",
provider: "Google",
role: "editor",
useAuth: true,
},
authorized: true,
},
{
name: "User with admin role is editor authorized",
fields: fields{
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return &chronograf.User{
ID: 1337,
Name: "billysteve",
Provider: "Google",
Scheme: "OAuth2",
Roles: []chronograf.Role{
server.AdminRole,
},
}, nil
},
},
Logger: clog.New(clog.DebugLevel),
},
args: args{
username: "billysteve",
provider: "Google",
role: "editor",
useAuth: true,
},
authorized: true,
},
{
name: "User with viewer role is admin unauthorized",
fields: fields{
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return &chronograf.User{
ID: 1337,
Name: "billysteve",
Provider: "Google",
Scheme: "OAuth2",
Roles: []chronograf.Role{
server.ViewerRole,
},
}, nil
},
},
Logger: clog.New(clog.DebugLevel),
},
args: args{
username: "billysteve",
provider: "Google",
role: "admin",
useAuth: true,
},
authorized: false,
},
{
name: "User with editor role is admin unauthorized",
fields: fields{
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return &chronograf.User{
ID: 1337,
Name: "billysteve",
Provider: "Google",
Scheme: "OAuth2",
Roles: []chronograf.Role{
server.EditorRole,
},
}, nil
},
},
Logger: clog.New(clog.DebugLevel),
},
args: args{
username: "billysteve",
provider: "Google",
role: "admin",
useAuth: true,
},
authorized: false,
},
{
name: "User with admin role is admin authorized",
fields: fields{
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return &chronograf.User{
ID: 1337,
Name: "billysteve",
Provider: "Google",
Scheme: "OAuth2",
Roles: []chronograf.Role{
server.AdminRole,
},
}, nil
},
},
Logger: clog.New(clog.DebugLevel),
},
args: args{
username: "billysteve",
provider: "Google",
role: "admin",
useAuth: true,
},
authorized: true,
},
{
name: "User with no role is viewer unauthorized",
fields: fields{
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return &chronograf.User{
ID: 1337,
Name: "billysteve",
Provider: "Google",
Scheme: "OAuth2",
Roles: []chronograf.Role{},
}, nil
},
},
Logger: clog.New(clog.DebugLevel),
},
args: args{
username: "billysteve",
provider: "Google",
role: "view",
useAuth: true,
},
authorized: false,
},
{
name: "User with no role is editor unauthorized",
fields: fields{
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return &chronograf.User{
ID: 1337,
Name: "billysteve",
Provider: "Google",
Scheme: "OAuth2",
Roles: []chronograf.Role{},
}, nil
},
},
Logger: clog.New(clog.DebugLevel),
},
args: args{
username: "billysteve",
provider: "Google",
role: "editor",
useAuth: true,
},
authorized: false,
},
{
name: "User with no role is admin unauthorized",
fields: fields{
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return &chronograf.User{
ID: 1337,
Name: "billysteve",
Provider: "Google",
Scheme: "OAuth2",
Roles: []chronograf.Role{},
}, nil
},
},
Logger: clog.New(clog.DebugLevel),
},
args: args{
username: "billysteve",
provider: "Google",
role: "admin",
useAuth: true,
},
authorized: false,
},
{
name: "User with unknown role is viewer unauthorized",
fields: fields{
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return &chronograf.User{
ID: 1337,
Name: "billysteve",
Provider: "Google",
Scheme: "OAuth2",
Roles: []chronograf.Role{
{
Name: "sweet_role",
},
},
}, nil
},
},
Logger: clog.New(clog.DebugLevel),
},
args: args{
username: "billysteve",
provider: "Google",
role: "viewer",
useAuth: true,
},
authorized: false,
},
{
name: "User with unknown role is editor unauthorized",
fields: fields{
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return &chronograf.User{
ID: 1337,
Name: "billysteve",
Provider: "Google",
Scheme: "OAuth2",
Roles: []chronograf.Role{
{
Name: "sweet_role",
},
},
}, nil
},
},
Logger: clog.New(clog.DebugLevel),
},
args: args{
username: "billysteve",
provider: "Google",
role: "editor",
useAuth: true,
},
authorized: false,
},
{
name: "User with unknown role is admin unauthorized",
fields: fields{
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return &chronograf.User{
ID: 1337,
Name: "billysteve",
Provider: "Google",
Scheme: "OAuth2",
Roles: []chronograf.Role{
{
Name: "sweet_role",
},
},
}, nil
},
},
Logger: clog.New(clog.DebugLevel),
},
args: args{
username: "billysteve",
provider: "Google",
role: "admin",
useAuth: true,
},
authorized: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var authorized bool
next := func(w http.ResponseWriter, r *http.Request) {
authorized = true
}
fn := server.AuthorizedUser(tt.fields.UsersStore, tt.args.useAuth, tt.args.role, tt.fields.Logger, next)
w := httptest.NewRecorder()
r := httptest.NewRequest(
"GET",
"http://any.url", // can be any valid URL as we are bypassing mux
nil,
)
r = r.WithContext(context.WithValue(r.Context(), oauth2.PrincipalKey, oauth2.Principal{
Subject: tt.args.username,
Issuer: tt.args.provider,
}))
fn(w, r)
if authorized != tt.authorized {
t.Errorf("%q. AuthorizedUser() = %v, expected %v", tt.name, authorized, tt.authorized)
}
})
}
}

View File

@ -36,7 +36,7 @@ func newMeResponse(usr *chronograf.User) meResponse {
}
}
func getEmail(ctx context.Context) (string, error) {
func getUsername(ctx context.Context) (string, error) {
principal, err := getPrincipal(ctx)
if err != nil {
return "", err
@ -47,6 +47,24 @@ func getEmail(ctx context.Context) (string, error) {
return principal.Subject, nil
}
func getProvider(ctx context.Context) (string, error) {
principal, err := getPrincipal(ctx)
if err != nil {
return "", err
}
if principal.Issuer == "" {
return "", fmt.Errorf("Token not found")
}
return principal.Issuer, nil
}
// TODO: This Scheme value is hard-coded temporarily since we only currently
// support OAuth2. This hard-coding should be removed whenever we add
// support for other authentication schemes.
func getScheme(ctx context.Context) (string, error) {
return "OAuth2", nil
}
func getPrincipal(ctx context.Context) (oauth2.Principal, error) {
principal, ok := ctx.Value(oauth2.PrincipalKey).(oauth2.Principal)
if !ok {
@ -56,7 +74,7 @@ func getPrincipal(ctx context.Context) (oauth2.Principal, error) {
return principal, nil
}
// Me does a findOrCreate based on the email in the context
// Me does a findOrCreate based on the username in the context
func (s *Service) Me(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if !s.UseAuth {
@ -66,14 +84,33 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) {
return
}
email, err := getEmail(ctx)
username, err := getUsername(ctx)
if err != nil {
invalidData(w, err, s.Logger)
return
}
provider, err := getProvider(ctx)
if err != nil {
invalidData(w, err, s.Logger)
return
}
scheme, err := getScheme(ctx)
if err != nil {
invalidData(w, err, s.Logger)
return
}
usr, err := s.UsersStore.Get(ctx, email)
if err == nil {
usr, err := s.UsersStore.Get(ctx, chronograf.UserQuery{
Name: &username,
Provider: &provider,
Scheme: &scheme,
})
if err != nil && err != chronograf.ErrUserNotFound {
unknownErrorWithMessage(w, err, s.Logger)
return
}
if usr != nil {
res := newMeResponse(usr)
encodeJSON(w, http.StatusOK, res, s.Logger)
return
@ -81,7 +118,12 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) {
// Because we didnt find a user, making a new one
user := &chronograf.User{
Name: email,
Name: username,
Provider: provider,
// TODO: This Scheme value is hard-coded temporarily since we only currently
// support OAuth2. This hard-coding should be removed whenever we add
// support for other authentication schemes.
Scheme: "OAuth2",
}
newUser, err := s.UsersStore.Add(ctx, user)

View File

@ -43,21 +43,27 @@ func TestService_Me(t *testing.T) {
},
fields: fields{
UseAuth: true,
Logger: log.New(log.DebugLevel),
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, name string) (*chronograf.User, error) {
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return &chronograf.User{
Name: "me",
Passwd: "hunter2",
Name: "me",
Provider: "GitHub",
Scheme: "OAuth2",
}, nil
},
},
},
principal: oauth2.Principal{
Subject: "me",
Issuer: "GitHub",
},
wantStatus: http.StatusOK,
wantContentType: "application/json",
wantBody: `{"name":"me","password":"hunter2","links":{"self":"/chronograf/v1/users/me"}}
wantBody: `{"name":"me","provider":"GitHub","scheme":"OAuth2","links":{"self":"/chronograf/v1/users/me"}}
`,
},
{
@ -68,9 +74,13 @@ func TestService_Me(t *testing.T) {
},
fields: fields{
UseAuth: true,
Logger: log.New(log.DebugLevel),
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, name string) (*chronograf.User, error) {
return nil, fmt.Errorf("Unknown User")
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
if q.Name == nil || q.Provider == nil || q.Scheme == nil {
return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme")
}
return nil, chronograf.ErrUserNotFound
},
AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) {
return u, nil
@ -79,10 +89,11 @@ func TestService_Me(t *testing.T) {
},
principal: oauth2.Principal{
Subject: "secret",
Issuer: "Auth0",
},
wantStatus: http.StatusOK,
wantContentType: "application/json",
wantBody: `{"name":"secret","links":{"self":"/chronograf/v1/users/secret"}}
wantBody: `{"name":"secret","provider":"Auth0","scheme":"OAuth2","links":{"self":"/chronograf/v1/users/secret"}}
`,
},
{
@ -94,8 +105,8 @@ func TestService_Me(t *testing.T) {
fields: fields{
UseAuth: true,
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, name string) (*chronograf.User, error) {
return nil, fmt.Errorf("Unknown User")
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
return nil, chronograf.ErrUserNotFound
},
AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) {
return nil, fmt.Errorf("Why Heavy?")
@ -105,6 +116,7 @@ func TestService_Me(t *testing.T) {
},
principal: oauth2.Principal{
Subject: "secret",
Issuer: "Heroku",
},
wantStatus: http.StatusInternalServerError,
wantContentType: "application/json",
@ -138,6 +150,7 @@ func TestService_Me(t *testing.T) {
wantStatus: http.StatusUnprocessableEntity,
principal: oauth2.Principal{
Subject: "",
Issuer: "",
},
},
}

View File

@ -67,127 +67,142 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
hr.NotFound = http.StripPrefix(opts.Basepath, hr.NotFound)
}
EnsureViewer := func(next http.HandlerFunc) http.HandlerFunc {
return AuthorizedUser(service.UsersStore, opts.UseAuth, ViewerRoleName, opts.Logger, next)
}
EnsureEditor := func(next http.HandlerFunc) http.HandlerFunc {
return AuthorizedUser(service.UsersStore, opts.UseAuth, EditorRoleName, opts.Logger, next)
}
EnsureAdmin := func(next http.HandlerFunc) http.HandlerFunc {
return AuthorizedUser(service.UsersStore, opts.UseAuth, AdminRoleName, opts.Logger, next)
}
/* Documentation */
router.GET("/swagger.json", Spec())
router.GET("/docs", Redoc("/swagger.json"))
/* API */
// Sources
router.GET("/chronograf/v1/sources", service.Sources)
router.POST("/chronograf/v1/sources", service.NewSource)
router.GET("/chronograf/v1/sources", EnsureViewer(service.Sources))
router.POST("/chronograf/v1/sources", EnsureEditor(service.NewSource))
router.GET("/chronograf/v1/sources/:id", service.SourcesID)
router.PATCH("/chronograf/v1/sources/:id", service.UpdateSource)
router.DELETE("/chronograf/v1/sources/:id", service.RemoveSource)
router.GET("/chronograf/v1/sources/:id", EnsureViewer(service.SourcesID))
router.PATCH("/chronograf/v1/sources/:id", EnsureEditor(service.UpdateSource))
router.DELETE("/chronograf/v1/sources/:id", EnsureEditor(service.RemoveSource))
// Source Proxy to Influx; Has gzip compression around the handler
influx := gziphandler.GzipHandler(http.HandlerFunc(service.Influx))
influx := gziphandler.GzipHandler(http.HandlerFunc(EnsureViewer(service.Influx)))
router.Handler("POST", "/chronograf/v1/sources/:id/proxy", influx)
// Write proxies line protocol write requests to InfluxDB
router.POST("/chronograf/v1/sources/:id/write", service.Write)
router.POST("/chronograf/v1/sources/:id/write", EnsureViewer(service.Write))
// Queries is used to analyze a specific queries
router.POST("/chronograf/v1/sources/:id/queries", service.Queries)
// Queries is used to analyze a specific queries and does not create any
// resources. It's a POST because Queries are POSTed to InfluxDB, but this
// only modifies InfluxDB resources with certain metaqueries, e.g. DROP DATABASE.
//
// Admins should ensure that the InfluxDB source as the proper permissions
// intended for Chronograf Users with the Viewer Role type.
router.POST("/chronograf/v1/sources/:id/queries", EnsureViewer(service.Queries))
// All possible permissions for users in this source
router.GET("/chronograf/v1/sources/:id/permissions", service.Permissions)
router.GET("/chronograf/v1/sources/:id/permissions", EnsureViewer(service.Permissions))
// Users associated with the data source
router.GET("/chronograf/v1/sources/:id/users", service.SourceUsers)
router.POST("/chronograf/v1/sources/:id/users", service.NewSourceUser)
router.GET("/chronograf/v1/sources/:id/users", EnsureAdmin(service.SourceUsers))
router.POST("/chronograf/v1/sources/:id/users", EnsureAdmin(service.NewSourceUser))
router.GET("/chronograf/v1/sources/:id/users/:uid", service.SourceUserID)
router.DELETE("/chronograf/v1/sources/:id/users/:uid", service.RemoveSourceUser)
router.PATCH("/chronograf/v1/sources/:id/users/:uid", service.UpdateSourceUser)
router.GET("/chronograf/v1/sources/:id/users/:uid", EnsureAdmin(service.SourceUserID))
router.DELETE("/chronograf/v1/sources/:id/users/:uid", EnsureAdmin(service.RemoveSourceUser))
router.PATCH("/chronograf/v1/sources/:id/users/:uid", EnsureAdmin(service.UpdateSourceUser))
// Roles associated with the data source
router.GET("/chronograf/v1/sources/:id/roles", service.SourceRoles)
router.POST("/chronograf/v1/sources/:id/roles", service.NewSourceRole)
router.GET("/chronograf/v1/sources/:id/roles", EnsureViewer(service.SourceRoles))
router.POST("/chronograf/v1/sources/:id/roles", EnsureEditor(service.NewSourceRole))
router.GET("/chronograf/v1/sources/:id/roles/:rid", service.SourceRoleID)
router.DELETE("/chronograf/v1/sources/:id/roles/:rid", service.RemoveSourceRole)
router.PATCH("/chronograf/v1/sources/:id/roles/:rid", service.UpdateSourceRole)
router.GET("/chronograf/v1/sources/:id/roles/:rid", EnsureViewer(service.SourceRoleID))
router.DELETE("/chronograf/v1/sources/:id/roles/:rid", EnsureEditor(service.RemoveSourceRole))
router.PATCH("/chronograf/v1/sources/:id/roles/:rid", EnsureEditor(service.UpdateSourceRole))
// Kapacitor
router.GET("/chronograf/v1/sources/:id/kapacitors", service.Kapacitors)
router.POST("/chronograf/v1/sources/:id/kapacitors", service.NewKapacitor)
router.GET("/chronograf/v1/sources/:id/kapacitors", EnsureViewer(service.Kapacitors))
router.POST("/chronograf/v1/sources/:id/kapacitors", EnsureEditor(service.NewKapacitor))
router.GET("/chronograf/v1/sources/:id/kapacitors/:kid", service.KapacitorsID)
router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid", service.UpdateKapacitor)
router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid", service.RemoveKapacitor)
router.GET("/chronograf/v1/sources/:id/kapacitors/:kid", EnsureViewer(service.KapacitorsID))
router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid", EnsureEditor(service.UpdateKapacitor))
router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid", EnsureEditor(service.RemoveKapacitor))
// Kapacitor rules
router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/rules", service.KapacitorRulesGet)
router.POST("/chronograf/v1/sources/:id/kapacitors/:kid/rules", service.KapacitorRulesPost)
router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/rules", EnsureViewer(service.KapacitorRulesGet))
router.POST("/chronograf/v1/sources/:id/kapacitors/:kid/rules", EnsureEditor(service.KapacitorRulesPost))
router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", service.KapacitorRulesID)
router.PUT("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", service.KapacitorRulesPut)
router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", service.KapacitorRulesStatus)
router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", service.KapacitorRulesDelete)
router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", EnsureViewer(service.KapacitorRulesID))
router.PUT("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", EnsureEditor(service.KapacitorRulesPut))
router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", EnsureEditor(service.KapacitorRulesStatus))
router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", EnsureEditor(service.KapacitorRulesDelete))
// Kapacitor Proxy
router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", service.KapacitorProxyGet)
router.POST("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", service.KapacitorProxyPost)
router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", service.KapacitorProxyPatch)
router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", service.KapacitorProxyDelete)
router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureViewer(service.KapacitorProxyGet))
router.POST("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.KapacitorProxyPost))
router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.KapacitorProxyPatch))
router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.KapacitorProxyDelete))
// Mappings
router.GET("/chronograf/v1/mappings", service.GetMappings)
router.GET("/chronograf/v1/mappings", EnsureViewer(service.GetMappings))
// Layouts
router.GET("/chronograf/v1/layouts", service.Layouts)
router.POST("/chronograf/v1/layouts", service.NewLayout)
router.GET("/chronograf/v1/layouts", EnsureViewer(service.Layouts))
router.POST("/chronograf/v1/layouts", EnsureEditor(service.NewLayout))
router.GET("/chronograf/v1/layouts/:id", service.LayoutsID)
router.PUT("/chronograf/v1/layouts/:id", service.UpdateLayout)
router.DELETE("/chronograf/v1/layouts/:id", service.RemoveLayout)
router.GET("/chronograf/v1/layouts/:id", EnsureViewer(service.LayoutsID))
router.PUT("/chronograf/v1/layouts/:id", EnsureEditor(service.UpdateLayout))
router.DELETE("/chronograf/v1/layouts/:id", EnsureEditor(service.RemoveLayout))
// Users associated with Chronograf
router.GET("/chronograf/v1/me", service.Me)
router.GET("/chronograf/v1/users", service.Users)
router.POST("/chronograf/v1/users", service.NewUser)
router.GET("/chronograf/v1/users", EnsureAdmin(service.Users))
router.POST("/chronograf/v1/users", EnsureAdmin(service.NewUser))
router.GET("/chronograf/v1/users/:id", service.UserID)
router.DELETE("/chronograf/v1/users/:id", service.RemoveUser)
router.PATCH("/chronograf/v1/users/:id", service.UpdateUser)
router.GET("/chronograf/v1/users/:id", EnsureAdmin(service.UserID))
router.DELETE("/chronograf/v1/users/:id", EnsureAdmin(service.RemoveUser))
router.PATCH("/chronograf/v1/users/:id", EnsureAdmin(service.UpdateUser))
// Dashboards
router.GET("/chronograf/v1/dashboards", service.Dashboards)
router.POST("/chronograf/v1/dashboards", service.NewDashboard)
router.GET("/chronograf/v1/dashboards", EnsureViewer(service.Dashboards))
router.POST("/chronograf/v1/dashboards", EnsureEditor(service.NewDashboard))
router.GET("/chronograf/v1/dashboards/:id", service.DashboardID)
router.DELETE("/chronograf/v1/dashboards/:id", service.RemoveDashboard)
router.PUT("/chronograf/v1/dashboards/:id", service.ReplaceDashboard)
router.PATCH("/chronograf/v1/dashboards/:id", service.UpdateDashboard)
router.GET("/chronograf/v1/dashboards/:id", EnsureViewer(service.DashboardID))
router.DELETE("/chronograf/v1/dashboards/:id", EnsureEditor(service.RemoveDashboard))
router.PUT("/chronograf/v1/dashboards/:id", EnsureEditor(service.ReplaceDashboard))
router.PATCH("/chronograf/v1/dashboards/:id", EnsureEditor(service.UpdateDashboard))
// Dashboard Cells
router.GET("/chronograf/v1/dashboards/:id/cells", service.DashboardCells)
router.POST("/chronograf/v1/dashboards/:id/cells", service.NewDashboardCell)
router.GET("/chronograf/v1/dashboards/:id/cells", EnsureViewer(service.DashboardCells))
router.POST("/chronograf/v1/dashboards/:id/cells", EnsureEditor(service.NewDashboardCell))
router.GET("/chronograf/v1/dashboards/:id/cells/:cid", service.DashboardCellID)
router.DELETE("/chronograf/v1/dashboards/:id/cells/:cid", service.RemoveDashboardCell)
router.PUT("/chronograf/v1/dashboards/:id/cells/:cid", service.ReplaceDashboardCell)
router.GET("/chronograf/v1/dashboards/:id/cells/:cid", EnsureViewer(service.DashboardCellID))
router.DELETE("/chronograf/v1/dashboards/:id/cells/:cid", EnsureEditor(service.RemoveDashboardCell))
router.PUT("/chronograf/v1/dashboards/:id/cells/:cid", EnsureEditor(service.ReplaceDashboardCell))
// Dashboard Templates
router.GET("/chronograf/v1/dashboards/:id/templates", service.Templates)
router.POST("/chronograf/v1/dashboards/:id/templates", service.NewTemplate)
router.GET("/chronograf/v1/dashboards/:id/templates", EnsureViewer(service.Templates))
router.POST("/chronograf/v1/dashboards/:id/templates", EnsureEditor(service.NewTemplate))
router.GET("/chronograf/v1/dashboards/:id/templates/:tid", service.TemplateID)
router.DELETE("/chronograf/v1/dashboards/:id/templates/:tid", service.RemoveTemplate)
router.PUT("/chronograf/v1/dashboards/:id/templates/:tid", service.ReplaceTemplate)
router.GET("/chronograf/v1/dashboards/:id/templates/:tid", EnsureViewer(service.TemplateID))
router.DELETE("/chronograf/v1/dashboards/:id/templates/:tid", EnsureEditor(service.RemoveTemplate))
router.PUT("/chronograf/v1/dashboards/:id/templates/:tid", EnsureEditor(service.ReplaceTemplate))
// Databases
router.GET("/chronograf/v1/sources/:id/dbs", service.GetDatabases)
router.POST("/chronograf/v1/sources/:id/dbs", service.NewDatabase)
router.GET("/chronograf/v1/sources/:id/dbs", EnsureViewer(service.GetDatabases))
router.POST("/chronograf/v1/sources/:id/dbs", EnsureEditor(service.NewDatabase))
router.DELETE("/chronograf/v1/sources/:id/dbs/:dbid", service.DropDatabase)
router.DELETE("/chronograf/v1/sources/:id/dbs/:dbid", EnsureEditor(service.DropDatabase))
// Retention Policies
router.GET("/chronograf/v1/sources/:id/dbs/:dbid/rps", service.RetentionPolicies)
router.POST("/chronograf/v1/sources/:id/dbs/:dbid/rps", service.NewRetentionPolicy)
router.GET("/chronograf/v1/sources/:id/dbs/:dbid/rps", EnsureViewer(service.RetentionPolicies))
router.POST("/chronograf/v1/sources/:id/dbs/:dbid/rps", EnsureEditor(service.NewRetentionPolicy))
router.PUT("/chronograf/v1/sources/:id/dbs/:dbid/rps/:rpid", service.UpdateRetentionPolicy)
router.DELETE("/chronograf/v1/sources/:id/dbs/:dbid/rps/:rpid", service.DropRetentionPolicy)
router.PUT("/chronograf/v1/sources/:id/dbs/:dbid/rps/:rpid", EnsureEditor(service.UpdateRetentionPolicy))
router.DELETE("/chronograf/v1/sources/:id/dbs/:dbid/rps/:rpid", EnsureEditor(service.DropRetentionPolicy))
allRoutes := &AllRoutes{
Logger: opts.Logger,

View File

@ -442,6 +442,7 @@ func (s *Service) SourceUsers(w http.ResponseWriter, r *http.Request) {
}
// SourceUserID retrieves a user with ID from store.
// In InfluxDB, a User's Name is their UID, hence the semantic below.
func (s *Service) SourceUserID(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
uid := httprouter.GetParamFromContext(ctx, "uid")
@ -451,7 +452,7 @@ func (s *Service) SourceUserID(w http.ResponseWriter, r *http.Request) {
return
}
store := ts.Users(ctx)
u, err := store.Get(ctx, uid)
u, err := store.Get(ctx, chronograf.UserQuery{Name: &uid})
if err != nil {
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
return
@ -514,7 +515,7 @@ func (s *Service) UpdateSourceUser(w http.ResponseWriter, r *http.Request) {
return
}
u, err := store.Get(ctx, uid)
u, err := store.Get(ctx, chronograf.UserQuery{Name: &uid})
if err != nil {
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
return

View File

@ -778,7 +778,7 @@ func TestService_SourceUserID(t *testing.T) {
},
UsersF: func(ctx context.Context) chronograf.UsersStore {
return &mocks.UsersStore{
GetF: func(ctx context.Context, uid string) (*chronograf.User, error) {
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
return &chronograf.User{
Name: "strickland",
Passwd: "discipline",
@ -833,7 +833,7 @@ func TestService_SourceUserID(t *testing.T) {
},
UsersF: func(ctx context.Context) chronograf.UsersStore {
return &mocks.UsersStore{
GetF: func(ctx context.Context, uid string) (*chronograf.User, error) {
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
return &chronograf.User{
Name: "strickland",
Passwd: "discipline",
@ -1041,7 +1041,7 @@ func TestService_UpdateSourceUser(t *testing.T) {
UpdateF: func(ctx context.Context, u *chronograf.User) error {
return nil
},
GetF: func(ctx context.Context, name string) (*chronograf.User, error) {
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
return &chronograf.User{
Name: "marty",
}, nil
@ -1093,7 +1093,7 @@ func TestService_UpdateSourceUser(t *testing.T) {
UpdateF: func(ctx context.Context, u *chronograf.User) error {
return nil
},
GetF: func(ctx context.Context, name string) (*chronograf.User, error) {
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
return &chronograf.User{
Name: "marty",
}, nil

View File

@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"sort"
"strconv"
"github.com/bouk/httprouter"
"github.com/influxdata/chronograf"
@ -28,6 +29,11 @@ func (r *userRequest) ValidCreate() error {
if r.Scheme == "" {
return fmt.Errorf("Scheme required on Chronograf User request body")
}
// TODO: This Scheme value is hard-coded temporarily since we only currently
// support OAuth2. This hard-coding should be removed whenever we add
// support for other authentication schemes.
r.Scheme = "OAuth2"
return r.ValidRoles()
}
@ -132,8 +138,13 @@ var (
func (s *Service) UserID(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id := httprouter.GetParamFromContext(ctx, "id")
user, err := s.UsersStore.Get(ctx, id)
idStr := httprouter.GetParamFromContext(ctx, "id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
Error(w, http.StatusBadRequest, fmt.Sprintf("invalid user id: %s", err.Error()), s.Logger)
return
}
user, err := s.UsersStore.Get(ctx, chronograf.UserQuery{ID: &id})
if err != nil {
Error(w, http.StatusBadRequest, err.Error(), s.Logger)
return
@ -178,9 +189,14 @@ func (s *Service) NewUser(w http.ResponseWriter, r *http.Request) {
// RemoveUser deletes a Chronograf user from store
func (s *Service) RemoveUser(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id := httprouter.GetParamFromContext(ctx, "id")
idStr := httprouter.GetParamFromContext(ctx, "id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
Error(w, http.StatusBadRequest, fmt.Sprintf("invalid user id: %s", err.Error()), s.Logger)
return
}
u, err := s.UsersStore.Get(ctx, id)
u, err := s.UsersStore.Get(ctx, chronograf.UserQuery{ID: &id})
if err != nil {
Error(w, http.StatusNotFound, err.Error(), s.Logger)
}
@ -205,9 +221,14 @@ func (s *Service) UpdateUser(w http.ResponseWriter, r *http.Request) {
}
ctx := r.Context()
id := httprouter.GetParamFromContext(ctx, "id")
idStr := httprouter.GetParamFromContext(ctx, "id")
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
Error(w, http.StatusBadRequest, fmt.Sprintf("invalid user id: %s", err.Error()), s.Logger)
return
}
u, err := s.UsersStore.Get(ctx, id)
u, err := s.UsersStore.Get(ctx, chronograf.UserQuery{ID: &id})
if err != nil {
Error(w, http.StatusNotFound, err.Error(), s.Logger)
}

View File

@ -47,9 +47,9 @@ func TestService_UserID(t *testing.T) {
fields: fields{
Logger: log.New(log.DebugLevel),
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, ID string) (*chronograf.User, error) {
switch ID {
case "1337":
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
switch *q.ID {
case 1337:
return &chronograf.User{
ID: 1337,
Name: "billysteve",
@ -60,7 +60,7 @@ func TestService_UserID(t *testing.T) {
},
}, nil
default:
return nil, fmt.Errorf("User with ID %s not found", ID)
return nil, fmt.Errorf("User with ID %s not found", *q.ID)
}
},
},
@ -212,9 +212,9 @@ func TestService_RemoveUser(t *testing.T) {
fields: fields{
Logger: log.New(log.DebugLevel),
UsersStore: &mocks.UsersStore{
GetF: func(ctx context.Context, ID string) (*chronograf.User, error) {
switch ID {
case "1339":
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
switch *q.ID {
case 1339:
return &chronograf.User{
ID: 1339,
Name: "helena",
@ -222,7 +222,7 @@ func TestService_RemoveUser(t *testing.T) {
Scheme: "LDAP",
}, nil
default:
return nil, fmt.Errorf("User with ID %s not found", ID)
return nil, fmt.Errorf("User with ID %s not found", *q.ID)
}
},
DeleteF: func(ctx context.Context, user *chronograf.User) error {
@ -303,9 +303,9 @@ func TestService_UpdateUser(t *testing.T) {
UpdateF: func(ctx context.Context, user *chronograf.User) error {
return nil
},
GetF: func(ctx context.Context, ID string) (*chronograf.User, error) {
switch ID {
case "1336":
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
switch *q.ID {
case 1336:
return &chronograf.User{
ID: 1336,
Name: "bobbetta2",
@ -316,7 +316,7 @@ func TestService_UpdateUser(t *testing.T) {
},
}, nil
default:
return nil, fmt.Errorf("User with ID %s not found", ID)
return nil, fmt.Errorf("User with ID %s not found", *q.ID)
}
},
},
@ -351,9 +351,9 @@ func TestService_UpdateUser(t *testing.T) {
UpdateF: func(ctx context.Context, user *chronograf.User) error {
return nil
},
GetF: func(ctx context.Context, ID string) (*chronograf.User, error) {
switch ID {
case "1336":
GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) {
switch *q.ID {
case 1336:
return &chronograf.User{
ID: 1336,
Name: "bobbetta2",
@ -361,7 +361,7 @@ func TestService_UpdateUser(t *testing.T) {
Scheme: "OAuth2",
}, nil
default:
return nil, fmt.Errorf("User with ID %s not found", ID)
return nil, fmt.Errorf("User with ID %s not found", *q.ID)
}
},
},