parent
4afb444579
commit
2d7828b602
|
@ -899,7 +899,7 @@ func TestServer(t *testing.T) {
|
|||
GithubClientSecret: "not empty",
|
||||
},
|
||||
method: "GET",
|
||||
path: "/chronograf/v1/users",
|
||||
path: "/chronograf/v1/organizations/default/users",
|
||||
principal: oauth2.Principal{
|
||||
Organization: "default",
|
||||
Subject: "billibob",
|
||||
|
@ -937,7 +937,7 @@ func TestServer(t *testing.T) {
|
|||
GithubClientSecret: "not empty",
|
||||
},
|
||||
method: "GET",
|
||||
path: "/chronograf/v1/users",
|
||||
path: "/chronograf/v1/organizations/default/users",
|
||||
principal: oauth2.Principal{
|
||||
Organization: "default",
|
||||
Subject: "billibob",
|
||||
|
@ -949,12 +949,12 @@ func TestServer(t *testing.T) {
|
|||
body: `
|
||||
{
|
||||
"links": {
|
||||
"self": "/chronograf/v1/users"
|
||||
"self": "/chronograf/v1/organizations/default/users"
|
||||
},
|
||||
"users": [
|
||||
{
|
||||
"links": {
|
||||
"self": "/chronograf/v1/users/1"
|
||||
"self": "/chronograf/v1/organizations/default/users/1"
|
||||
},
|
||||
"id": "1",
|
||||
"name": "billibob",
|
||||
|
@ -1011,7 +1011,7 @@ func TestServer(t *testing.T) {
|
|||
GithubClientSecret: "not empty",
|
||||
},
|
||||
method: "GET",
|
||||
path: "/chronograf/v1/users",
|
||||
path: "/chronograf/v1/organizations/default/users",
|
||||
principal: oauth2.Principal{
|
||||
Organization: "default",
|
||||
Subject: "billibob",
|
||||
|
@ -1023,12 +1023,12 @@ func TestServer(t *testing.T) {
|
|||
body: `
|
||||
{
|
||||
"links": {
|
||||
"self": "/chronograf/v1/users"
|
||||
"self": "/chronograf/v1/organizations/default/users"
|
||||
},
|
||||
"users": [
|
||||
{
|
||||
"links": {
|
||||
"self": "/chronograf/v1/users/1"
|
||||
"self": "/chronograf/v1/organizations/default/users/1"
|
||||
},
|
||||
"id": "1",
|
||||
"name": "billibob",
|
||||
|
@ -1084,7 +1084,7 @@ func TestServer(t *testing.T) {
|
|||
},
|
||||
},
|
||||
method: "POST",
|
||||
path: "/chronograf/v1/users?raw=true",
|
||||
path: "/chronograf/v1/users",
|
||||
principal: oauth2.Principal{
|
||||
Organization: "default",
|
||||
Subject: "billibob",
|
||||
|
@ -1096,7 +1096,7 @@ func TestServer(t *testing.T) {
|
|||
body: `
|
||||
{
|
||||
"links": {
|
||||
"self": "/chronograf/v1/users/2?raw=true"
|
||||
"self": "/chronograf/v1/users/2"
|
||||
},
|
||||
"id": "2",
|
||||
"name": "user",
|
||||
|
@ -1145,7 +1145,7 @@ func TestServer(t *testing.T) {
|
|||
Roles: []chronograf.Role{},
|
||||
},
|
||||
method: "POST",
|
||||
path: "/chronograf/v1/users?raw=true",
|
||||
path: "/chronograf/v1/users",
|
||||
principal: oauth2.Principal{
|
||||
Organization: "default",
|
||||
Subject: "billibob",
|
||||
|
@ -1157,7 +1157,7 @@ func TestServer(t *testing.T) {
|
|||
body: `
|
||||
{
|
||||
"links": {
|
||||
"self": "/chronograf/v1/users/2?raw=true"
|
||||
"self": "/chronograf/v1/users/2"
|
||||
},
|
||||
"id": "2",
|
||||
"name": "user",
|
||||
|
@ -1215,7 +1215,7 @@ func TestServer(t *testing.T) {
|
|||
GithubClientSecret: "not empty",
|
||||
},
|
||||
method: "GET",
|
||||
path: "/chronograf/v1/users?raw=true",
|
||||
path: "/chronograf/v1/users",
|
||||
principal: oauth2.Principal{
|
||||
Organization: "default",
|
||||
Subject: "billibob",
|
||||
|
@ -1227,12 +1227,12 @@ func TestServer(t *testing.T) {
|
|||
body: `
|
||||
{
|
||||
"links": {
|
||||
"self": "/chronograf/v1/users?raw=true"
|
||||
"self": "/chronograf/v1/users"
|
||||
},
|
||||
"users": [
|
||||
{
|
||||
"links": {
|
||||
"self": "/chronograf/v1/users/1?raw=true"
|
||||
"self": "/chronograf/v1/users/1"
|
||||
},
|
||||
"id": "1",
|
||||
"name": "billibob",
|
||||
|
@ -1248,7 +1248,7 @@ func TestServer(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"links": {
|
||||
"self": "/chronograf/v1/users/2?raw=true"
|
||||
"self": "/chronograf/v1/users/2"
|
||||
},
|
||||
"id": "2",
|
||||
"name": "billietta",
|
||||
|
@ -1317,7 +1317,7 @@ func TestServer(t *testing.T) {
|
|||
GithubClientSecret: "not empty",
|
||||
},
|
||||
method: "GET",
|
||||
path: "/chronograf/v1/users?raw=true",
|
||||
path: "/chronograf/v1/users",
|
||||
principal: oauth2.Principal{
|
||||
Organization: "default",
|
||||
Subject: "billieta",
|
||||
|
@ -1365,7 +1365,7 @@ func TestServer(t *testing.T) {
|
|||
GithubClientSecret: "not empty",
|
||||
},
|
||||
method: "POST",
|
||||
path: "/chronograf/v1/users",
|
||||
path: "/chronograf/v1/organizations/default/users",
|
||||
payload: &chronograf.User{
|
||||
Name: "user",
|
||||
Provider: "provider",
|
||||
|
@ -1388,7 +1388,7 @@ func TestServer(t *testing.T) {
|
|||
body: `
|
||||
{
|
||||
"links": {
|
||||
"self": "/chronograf/v1/users/2"
|
||||
"self": "/chronograf/v1/organizations/default/users/2"
|
||||
},
|
||||
"id": "2",
|
||||
"name": "user",
|
||||
|
@ -1435,7 +1435,7 @@ func TestServer(t *testing.T) {
|
|||
GithubClientSecret: "not empty",
|
||||
},
|
||||
method: "POST",
|
||||
path: "/chronograf/v1/users",
|
||||
path: "/chronograf/v1/organizations/default/users",
|
||||
payload: &chronograf.User{
|
||||
Name: "user",
|
||||
Provider: "provider",
|
||||
|
@ -1458,7 +1458,7 @@ func TestServer(t *testing.T) {
|
|||
body: `
|
||||
{
|
||||
"links": {
|
||||
"self": "/chronograf/v1/users/2"
|
||||
"self": "/chronograf/v1/organizations/default/users/2"
|
||||
},
|
||||
"id": "2",
|
||||
"name": "user",
|
||||
|
@ -1505,7 +1505,7 @@ func TestServer(t *testing.T) {
|
|||
GithubClientSecret: "not empty",
|
||||
},
|
||||
method: "POST",
|
||||
path: "/chronograf/v1/users",
|
||||
path: "/chronograf/v1/organizations/default/users",
|
||||
payload: &chronograf.User{
|
||||
Name: "user",
|
||||
Provider: "provider",
|
||||
|
@ -1528,7 +1528,7 @@ func TestServer(t *testing.T) {
|
|||
body: `
|
||||
{
|
||||
"links": {
|
||||
"self": "/chronograf/v1/users/2"
|
||||
"self": "/chronograf/v1/organizations/default/users/2"
|
||||
},
|
||||
"id": "2",
|
||||
"name": "user",
|
||||
|
@ -1575,7 +1575,7 @@ func TestServer(t *testing.T) {
|
|||
GithubClientSecret: "not empty",
|
||||
},
|
||||
method: "POST",
|
||||
path: "/chronograf/v1/users",
|
||||
path: "/chronograf/v1/organizations/default/users",
|
||||
payload: &chronograf.User{
|
||||
Name: "user",
|
||||
Provider: "provider",
|
||||
|
@ -1641,7 +1641,7 @@ func TestServer(t *testing.T) {
|
|||
GithubClientSecret: "not empty",
|
||||
},
|
||||
method: "POST",
|
||||
path: "/chronograf/v1/users?raw=true",
|
||||
path: "/chronograf/v1/users",
|
||||
payload: &chronograf.User{
|
||||
Name: "user",
|
||||
Provider: "provider",
|
||||
|
@ -1668,7 +1668,7 @@ func TestServer(t *testing.T) {
|
|||
body: `
|
||||
{
|
||||
"links": {
|
||||
"self": "/chronograf/v1/users/2?raw=true"
|
||||
"self": "/chronograf/v1/users/2"
|
||||
},
|
||||
"id": "2",
|
||||
"name": "user",
|
||||
|
@ -1726,7 +1726,7 @@ func TestServer(t *testing.T) {
|
|||
GithubClientSecret: "not empty",
|
||||
},
|
||||
method: "PATCH",
|
||||
path: "/chronograf/v1/users/1?raw=true",
|
||||
path: "/chronograf/v1/users/1",
|
||||
payload: map[string]interface{}{
|
||||
"name": "billibob",
|
||||
"provider": "github",
|
||||
|
@ -1745,7 +1745,7 @@ func TestServer(t *testing.T) {
|
|||
body: `
|
||||
{
|
||||
"links": {
|
||||
"self": "/chronograf/v1/users/1?raw=true"
|
||||
"self": "/chronograf/v1/users/1"
|
||||
},
|
||||
"id": "1",
|
||||
"name": "billibob",
|
||||
|
@ -1795,7 +1795,7 @@ func TestServer(t *testing.T) {
|
|||
GithubClientSecret: "not empty",
|
||||
},
|
||||
method: "PATCH",
|
||||
path: "/chronograf/v1/users/1?raw=true",
|
||||
path: "/chronograf/v1/users/1",
|
||||
payload: &chronograf.User{
|
||||
Name: "billibob",
|
||||
Provider: "github",
|
||||
|
@ -1823,7 +1823,7 @@ func TestServer(t *testing.T) {
|
|||
body: `
|
||||
{
|
||||
"links": {
|
||||
"self": "/chronograf/v1/users/1?raw=true"
|
||||
"self": "/chronograf/v1/users/1"
|
||||
},
|
||||
"id": "1",
|
||||
"name": "billibob",
|
||||
|
@ -1869,7 +1869,7 @@ func TestServer(t *testing.T) {
|
|||
GithubClientSecret: "not empty",
|
||||
},
|
||||
method: "PATCH",
|
||||
path: "/chronograf/v1/users/1",
|
||||
path: "/chronograf/v1/organizations/default/users/1",
|
||||
payload: map[string]interface{}{
|
||||
"id": "1",
|
||||
"superAdmin": false,
|
||||
|
@ -1964,7 +1964,7 @@ func TestServer(t *testing.T) {
|
|||
"scheme": "oauth2",
|
||||
"superAdmin": true,
|
||||
"links": {
|
||||
"self": "/chronograf/v1/users/1"
|
||||
"self": "/chronograf/v1/organizations/1/users/1"
|
||||
},
|
||||
"organizations": [
|
||||
{
|
||||
|
|
|
@ -11,11 +11,6 @@ import (
|
|||
"github.com/influxdata/chronograf/roles"
|
||||
)
|
||||
|
||||
const (
|
||||
rawQueryKey = "raw"
|
||||
rawQueryValue = "true"
|
||||
)
|
||||
|
||||
// AuthorizedToken extracts the token and validates; if valid the next handler
|
||||
// will be run. The principal will be sent to the next handler via the request's
|
||||
// Context. It is up to the next handler to determine if the principal has access.
|
||||
|
@ -54,11 +49,8 @@ func AuthorizedToken(auth oauth2.Authenticator, logger chronograf.Logger, next h
|
|||
})
|
||||
}
|
||||
|
||||
// CheckRaw checks the query parameters on the HTTP request looking for
|
||||
// the pair raw=true. If it finds a pair and the user is a super admin,
|
||||
// then the user making the request will be given raw access to the data
|
||||
// store (usually users are given a facade).
|
||||
func CheckForRawQuery(logger chronograf.Logger, next http.HandlerFunc) http.HandlerFunc {
|
||||
// RawStoreAccess gives a super admin access to the data store without a facade.
|
||||
func RawStoreAccess(logger chronograf.Logger, next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
if isServer := hasServerContext(ctx); isServer {
|
||||
|
@ -67,20 +59,17 @@ func CheckForRawQuery(logger chronograf.Logger, next http.HandlerFunc) http.Hand
|
|||
}
|
||||
|
||||
log := logger.
|
||||
WithField("component", "raw_query").
|
||||
WithField("component", "raw_store").
|
||||
WithField("remote_addr", r.RemoteAddr).
|
||||
WithField("method", r.Method).
|
||||
WithField("url", r.URL)
|
||||
|
||||
v := r.URL.Query().Get(rawQueryKey)
|
||||
if v == rawQueryValue {
|
||||
if isSuperAdmin := hasSuperAdminContext(ctx); isSuperAdmin {
|
||||
r = r.WithContext(serverContext(ctx))
|
||||
} else {
|
||||
log.Error("User making request is not a SuperAdmin")
|
||||
Error(w, http.StatusForbidden, "User is not authorized", logger)
|
||||
return
|
||||
}
|
||||
if isSuperAdmin := hasSuperAdminContext(ctx); isSuperAdmin {
|
||||
r = r.WithContext(serverContext(ctx))
|
||||
} else {
|
||||
log.Error("User making request is not a SuperAdmin")
|
||||
Error(w, http.StatusForbidden, "User is not authorized", logger)
|
||||
return
|
||||
}
|
||||
|
||||
next(w, r)
|
||||
|
@ -219,6 +208,13 @@ func hasAuthorizedRole(u *chronograf.User, role string) bool {
|
|||
}
|
||||
|
||||
switch role {
|
||||
case roles.MemberRoleName:
|
||||
for _, r := range u.Roles {
|
||||
switch r.Name {
|
||||
case roles.MemberRoleName, roles.ViewerRoleName, roles.EditorRoleName, roles.AdminRoleName:
|
||||
return true
|
||||
}
|
||||
}
|
||||
case roles.ViewerRoleName:
|
||||
for _, r := range u.Roles {
|
||||
switch r.Name {
|
||||
|
|
|
@ -108,6 +108,230 @@ func TestAuthorizedUser(t *testing.T) {
|
|||
hasServerContext: true,
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
name: "User with member role is member 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{
|
||||
{
|
||||
Name: roles.MemberRoleName,
|
||||
Organization: "1337",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
OrganizationsStore: &mocks.OrganizationsStore{
|
||||
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
|
||||
return &chronograf.Organization{
|
||||
ID: "0",
|
||||
}, nil
|
||||
},
|
||||
GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) {
|
||||
if q.ID == nil {
|
||||
return nil, fmt.Errorf("Invalid organization query: missing ID")
|
||||
}
|
||||
return &chronograf.Organization{
|
||||
ID: "1337",
|
||||
Name: "The ShillBillThrilliettas",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
Logger: clog.New(clog.DebugLevel),
|
||||
},
|
||||
args: args{
|
||||
principal: &oauth2.Principal{
|
||||
Subject: "billysteve",
|
||||
Issuer: "google",
|
||||
Organization: "1337",
|
||||
},
|
||||
scheme: "oauth2",
|
||||
role: "member",
|
||||
useAuth: true,
|
||||
},
|
||||
authorized: true,
|
||||
hasOrganizationContext: true,
|
||||
hasSuperAdminContext: false,
|
||||
hasRoleContext: true,
|
||||
hasServerContext: false,
|
||||
},
|
||||
{
|
||||
name: "User with viewer role is member 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{
|
||||
{
|
||||
Name: roles.ViewerRoleName,
|
||||
Organization: "1337",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
OrganizationsStore: &mocks.OrganizationsStore{
|
||||
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
|
||||
return &chronograf.Organization{
|
||||
ID: "0",
|
||||
}, nil
|
||||
},
|
||||
GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) {
|
||||
if q.ID == nil {
|
||||
return nil, fmt.Errorf("Invalid organization query: missing ID")
|
||||
}
|
||||
return &chronograf.Organization{
|
||||
ID: "1337",
|
||||
Name: "The ShillBillThrilliettas",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
Logger: clog.New(clog.DebugLevel),
|
||||
},
|
||||
args: args{
|
||||
principal: &oauth2.Principal{
|
||||
Subject: "billysteve",
|
||||
Issuer: "google",
|
||||
Organization: "1337",
|
||||
},
|
||||
scheme: "oauth2",
|
||||
role: "member",
|
||||
useAuth: true,
|
||||
},
|
||||
authorized: true,
|
||||
hasOrganizationContext: true,
|
||||
hasSuperAdminContext: false,
|
||||
hasRoleContext: true,
|
||||
hasServerContext: false,
|
||||
},
|
||||
{
|
||||
name: "User with editor role is member 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{
|
||||
{
|
||||
Name: roles.EditorRoleName,
|
||||
Organization: "1337",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
OrganizationsStore: &mocks.OrganizationsStore{
|
||||
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
|
||||
return &chronograf.Organization{
|
||||
ID: "0",
|
||||
}, nil
|
||||
},
|
||||
GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) {
|
||||
if q.ID == nil {
|
||||
return nil, fmt.Errorf("Invalid organization query: missing ID")
|
||||
}
|
||||
return &chronograf.Organization{
|
||||
ID: "1337",
|
||||
Name: "The ShillBillThrilliettas",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
Logger: clog.New(clog.DebugLevel),
|
||||
},
|
||||
args: args{
|
||||
principal: &oauth2.Principal{
|
||||
Subject: "billysteve",
|
||||
Issuer: "google",
|
||||
Organization: "1337",
|
||||
},
|
||||
scheme: "oauth2",
|
||||
role: "member",
|
||||
useAuth: true,
|
||||
},
|
||||
authorized: true,
|
||||
hasOrganizationContext: true,
|
||||
hasSuperAdminContext: false,
|
||||
hasRoleContext: true,
|
||||
hasServerContext: false,
|
||||
},
|
||||
{
|
||||
name: "User with admin role is member 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{
|
||||
{
|
||||
Name: roles.AdminRoleName,
|
||||
Organization: "1337",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
OrganizationsStore: &mocks.OrganizationsStore{
|
||||
DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) {
|
||||
return &chronograf.Organization{
|
||||
ID: "0",
|
||||
}, nil
|
||||
},
|
||||
GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) {
|
||||
if q.ID == nil {
|
||||
return nil, fmt.Errorf("Invalid organization query: missing ID")
|
||||
}
|
||||
return &chronograf.Organization{
|
||||
ID: "1337",
|
||||
Name: "The ShillBillThrilliettas",
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
Logger: clog.New(clog.DebugLevel),
|
||||
},
|
||||
args: args{
|
||||
principal: &oauth2.Principal{
|
||||
Subject: "billysteve",
|
||||
Issuer: "google",
|
||||
Organization: "1337",
|
||||
},
|
||||
scheme: "oauth2",
|
||||
role: "member",
|
||||
useAuth: true,
|
||||
},
|
||||
authorized: true,
|
||||
hasOrganizationContext: true,
|
||||
hasSuperAdminContext: false,
|
||||
hasRoleContext: true,
|
||||
hasServerContext: false,
|
||||
},
|
||||
{
|
||||
name: "User with viewer role is viewer authorized",
|
||||
fields: fields{
|
||||
|
@ -1607,7 +1831,7 @@ func TestAuthorizedUser(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCheckForRawQuery(t *testing.T) {
|
||||
func TestRawStoreAccess(t *testing.T) {
|
||||
type fields struct {
|
||||
Logger chronograf.Logger
|
||||
}
|
||||
|
@ -1615,7 +1839,6 @@ func TestCheckForRawQuery(t *testing.T) {
|
|||
principal *oauth2.Principal
|
||||
serverContext bool
|
||||
user *chronograf.User
|
||||
raw bool
|
||||
}
|
||||
type wants struct {
|
||||
authorized bool
|
||||
|
@ -1628,13 +1851,12 @@ func TestCheckForRawQuery(t *testing.T) {
|
|||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "middleware already has server context with raw",
|
||||
name: "middleware already has server context",
|
||||
fields: fields{
|
||||
Logger: clog.New(clog.DebugLevel),
|
||||
},
|
||||
args: args{
|
||||
serverContext: true,
|
||||
raw: true,
|
||||
},
|
||||
wants: wants{
|
||||
authorized: true,
|
||||
|
@ -1642,21 +1864,7 @@ func TestCheckForRawQuery(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "middleware already has server context without raw",
|
||||
fields: fields{
|
||||
Logger: clog.New(clog.DebugLevel),
|
||||
},
|
||||
args: args{
|
||||
serverContext: true,
|
||||
raw: false,
|
||||
},
|
||||
wants: wants{
|
||||
authorized: true,
|
||||
hasServerContext: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user on context is a SuperAdmin with raw",
|
||||
name: "user on context is a SuperAdmin",
|
||||
fields: fields{
|
||||
Logger: clog.New(clog.DebugLevel),
|
||||
},
|
||||
|
@ -1664,7 +1872,6 @@ func TestCheckForRawQuery(t *testing.T) {
|
|||
user: &chronograf.User{
|
||||
SuperAdmin: true,
|
||||
},
|
||||
raw: true,
|
||||
},
|
||||
wants: wants{
|
||||
authorized: true,
|
||||
|
@ -1672,7 +1879,7 @@ func TestCheckForRawQuery(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "user on context is a not SuperAdmin with raw",
|
||||
name: "user on context is a not SuperAdmin",
|
||||
fields: fields{
|
||||
Logger: clog.New(clog.DebugLevel),
|
||||
},
|
||||
|
@ -1680,29 +1887,12 @@ func TestCheckForRawQuery(t *testing.T) {
|
|||
user: &chronograf.User{
|
||||
SuperAdmin: false,
|
||||
},
|
||||
raw: true,
|
||||
},
|
||||
wants: wants{
|
||||
authorized: false,
|
||||
hasServerContext: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user on context is a SuperAdmin without raw",
|
||||
fields: fields{
|
||||
Logger: clog.New(clog.DebugLevel),
|
||||
},
|
||||
args: args{
|
||||
user: &chronograf.User{
|
||||
SuperAdmin: true,
|
||||
},
|
||||
raw: false,
|
||||
},
|
||||
wants: wants{
|
||||
authorized: true,
|
||||
hasServerContext: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@ -1714,16 +1904,13 @@ func TestCheckForRawQuery(t *testing.T) {
|
|||
hasServerCtx = hasServerContext(ctx)
|
||||
authorized = true
|
||||
}
|
||||
fn := CheckForRawQuery(
|
||||
fn := RawStoreAccess(
|
||||
tt.fields.Logger,
|
||||
next,
|
||||
)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
url := "http://any.url"
|
||||
if tt.args.raw {
|
||||
url = fmt.Sprintf("%s?raw=true", url)
|
||||
}
|
||||
r := httptest.NewRequest(
|
||||
"GET",
|
||||
url,
|
||||
|
@ -1744,15 +1931,15 @@ func TestCheckForRawQuery(t *testing.T) {
|
|||
fn(w, r)
|
||||
|
||||
if authorized != tt.wants.authorized {
|
||||
t.Errorf("%q. CheckForRawQuery() = %v, expected %v", tt.name, authorized, tt.wants.authorized)
|
||||
t.Errorf("%q. RawStoreAccess() = %v, expected %v", tt.name, authorized, tt.wants.authorized)
|
||||
}
|
||||
|
||||
if !authorized && w.Code != http.StatusForbidden {
|
||||
t.Errorf("%q. CheckForRawQuery() Status Code = %v, expected %v", tt.name, w.Code, http.StatusForbidden)
|
||||
t.Errorf("%q. RawStoreAccess() Status Code = %v, expected %v", tt.name, w.Code, http.StatusForbidden)
|
||||
}
|
||||
|
||||
if hasServerCtx != tt.wants.hasServerContext {
|
||||
t.Errorf("%q. CheckForRawQuery().Context().Server = %v, expected %v", tt.name, hasServerCtx, tt.wants.hasServerContext)
|
||||
t.Errorf("%q. RawStoreAccess().Context().Server = %v, expected %v", tt.name, hasServerCtx, tt.wants.hasServerContext)
|
||||
}
|
||||
|
||||
})
|
||||
|
|
11
server/me.go
11
server/me.go
|
@ -26,10 +26,11 @@ type meResponse struct {
|
|||
|
||||
// If new user response is nil, return an empty meResponse because it
|
||||
// indicates authentication is not needed
|
||||
func newMeResponse(usr *chronograf.User) meResponse {
|
||||
base := "/chronograf/v1/users"
|
||||
func newMeResponse(usr *chronograf.User, org string) meResponse {
|
||||
base := "/chronograf/v1"
|
||||
name := "me"
|
||||
if usr != nil {
|
||||
base = fmt.Sprintf("/chronograf/v1/organizations/%s/users", org)
|
||||
name = PathEscape(fmt.Sprintf("%d", usr.ID))
|
||||
}
|
||||
|
||||
|
@ -181,7 +182,7 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) {
|
|||
ctx := r.Context()
|
||||
if !s.UseAuth {
|
||||
// If there's no authentication, return an empty user
|
||||
res := newMeResponse(nil)
|
||||
res := newMeResponse(nil, "")
|
||||
encodeJSON(w, http.StatusOK, res, s.Logger)
|
||||
return
|
||||
}
|
||||
|
@ -264,7 +265,7 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) {
|
|||
unknownErrorWithMessage(w, err, s.Logger)
|
||||
return
|
||||
}
|
||||
res := newMeResponse(usr)
|
||||
res := newMeResponse(usr, currentOrg.ID)
|
||||
res.Organizations = orgs
|
||||
res.CurrentOrganization = currentOrg
|
||||
encodeJSON(w, http.StatusOK, res, s.Logger)
|
||||
|
@ -314,7 +315,7 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) {
|
|||
unknownErrorWithMessage(w, err, s.Logger)
|
||||
return
|
||||
}
|
||||
res := newMeResponse(newUser)
|
||||
res := newMeResponse(newUser, currentOrg.ID)
|
||||
res.Organizations = orgs
|
||||
res.CurrentOrganization = currentOrg
|
||||
encodeJSON(w, http.StatusOK, res, s.Logger)
|
||||
|
|
|
@ -176,7 +176,7 @@ func TestService_Me(t *testing.T) {
|
|||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"name":"me","roles":[{"name":"viewer","organization":"0"}],"provider":"github","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"viewer","public":true}],"currentOrganization":{"id":"0","name":"Default","defaultRole":"viewer","public":true}}`,
|
||||
wantBody: `{"name":"me","roles":[{"name":"viewer","organization":"0"}],"provider":"github","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/organizations/0/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"viewer","public":true}],"currentOrganization":{"id":"0","name":"Default","defaultRole":"viewer","public":true}}`,
|
||||
},
|
||||
{
|
||||
name: "Existing user - private default org",
|
||||
|
@ -306,7 +306,7 @@ func TestService_Me(t *testing.T) {
|
|||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"name":"me","roles":[{"name":"viewer","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"viewer","public":true}],"currentOrganization":{"id":"0","name":"Default","defaultRole":"viewer","public":true}}`,
|
||||
wantBody: `{"name":"me","roles":[{"name":"viewer","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/organizations/0/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"viewer","public":true}],"currentOrganization":{"id":"0","name":"Default","defaultRole":"viewer","public":true}}`,
|
||||
},
|
||||
{
|
||||
name: "Existing user - organization doesn't exist",
|
||||
|
@ -423,7 +423,7 @@ func TestService_Me(t *testing.T) {
|
|||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"name":"secret","superAdmin":true,"roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true}}
|
||||
wantBody: `{"name":"secret","superAdmin":true,"roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/organizations/0/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true}}
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -485,7 +485,7 @@ func TestService_Me(t *testing.T) {
|
|||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","public":true,"defaultRole":"viewer"}],"currentOrganization":{"id":"0","name":"The Gnarly Default","public":true,"defaultRole":"viewer"}}
|
||||
wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/organizations/0/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","public":true,"defaultRole":"viewer"}],"currentOrganization":{"id":"0","name":"The Gnarly Default","public":true,"defaultRole":"viewer"}}
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -547,7 +547,7 @@ func TestService_Me(t *testing.T) {
|
|||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"name":"secret","superAdmin":true,"roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","public":true,"defaultRole":"viewer"}],"currentOrganization":{"id":"0","name":"The Gnarly Default","public":true,"defaultRole":"viewer"}}
|
||||
wantBody: `{"name":"secret","superAdmin":true,"roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/organizations/0/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","public":true,"defaultRole":"viewer"}],"currentOrganization":{"id":"0","name":"The Gnarly Default","public":true,"defaultRole":"viewer"}}
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -624,7 +624,7 @@ func TestService_Me(t *testing.T) {
|
|||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"links":{"self":"/chronograf/v1/users/me"}}
|
||||
wantBody: `{"links":{"self":"/chronograf/v1/me"}}
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -824,7 +824,7 @@ func TestService_UpdateMe(t *testing.T) {
|
|||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"admin","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"admin","public":true},{"id":"1337","name":"The ShillBillThrilliettas","public":true}],"currentOrganization":{"id":"1337","name":"The ShillBillThrilliettas","public":true}}`,
|
||||
wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"admin","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/organizations/1337/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"admin","public":true},{"id":"1337","name":"The ShillBillThrilliettas","public":true}],"currentOrganization":{"id":"1337","name":"The ShillBillThrilliettas","public":true}}`,
|
||||
},
|
||||
{
|
||||
name: "Change the current User's organization",
|
||||
|
@ -899,7 +899,7 @@ func TestService_UpdateMe(t *testing.T) {
|
|||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantContentType: "application/json",
|
||||
wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"editor","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"editor","public":true},{"id":"1337","name":"The ThrillShilliettos","public":false}],"currentOrganization":{"id":"1337","name":"The ThrillShilliettos","public":false}}`,
|
||||
wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"editor","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/organizations/1337/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"editor","public":true},{"id":"1337","name":"The ThrillShilliettos","public":false}],"currentOrganization":{"id":"1337","name":"The ThrillShilliettos","public":false}}`,
|
||||
},
|
||||
{
|
||||
name: "Unable to find requested user in valid organization",
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/bouk/httprouter"
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
// RouteMatchesPrincipal checks that the organization on context matches the organization
|
||||
// in the route.
|
||||
func RouteMatchesPrincipal(
|
||||
useAuth bool,
|
||||
logger chronograf.Logger,
|
||||
next http.HandlerFunc,
|
||||
) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
if !useAuth {
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
log := logger.
|
||||
WithField("component", "org_match").
|
||||
WithField("remote_addr", r.RemoteAddr).
|
||||
WithField("method", r.Method).
|
||||
WithField("url", r.URL)
|
||||
|
||||
orgID := httprouter.GetParamFromContext(ctx, "oid")
|
||||
p, err := getValidPrincipal(ctx)
|
||||
if err != nil {
|
||||
log.Error("Failed to retrieve principal from context")
|
||||
Error(w, http.StatusForbidden, "User is not authorized", logger)
|
||||
return
|
||||
}
|
||||
|
||||
if orgID != p.Organization {
|
||||
log.Error("Route organization does not match the organization on principal")
|
||||
Error(w, http.StatusForbidden, "User is not authorized", logger)
|
||||
return
|
||||
}
|
||||
|
||||
next(w, r)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/bouk/httprouter"
|
||||
"github.com/influxdata/chronograf"
|
||||
"github.com/influxdata/chronograf/log"
|
||||
"github.com/influxdata/chronograf/oauth2"
|
||||
)
|
||||
|
||||
func TestRouteMatchesPrincipal(t *testing.T) {
|
||||
type fields struct {
|
||||
Logger chronograf.Logger
|
||||
}
|
||||
type args struct {
|
||||
useAuth bool
|
||||
principal *oauth2.Principal
|
||||
routerParams *httprouter.Params
|
||||
}
|
||||
type wants struct {
|
||||
matches bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wants wants
|
||||
}{
|
||||
{
|
||||
name: "route matches request params",
|
||||
fields: fields{
|
||||
Logger: log.New(log.DebugLevel),
|
||||
},
|
||||
args: args{
|
||||
useAuth: true,
|
||||
principal: &oauth2.Principal{
|
||||
Subject: "user",
|
||||
Issuer: "github",
|
||||
Organization: "default",
|
||||
},
|
||||
routerParams: &httprouter.Params{
|
||||
{
|
||||
Key: "oid",
|
||||
Value: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
matches: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "route does not match request params",
|
||||
fields: fields{
|
||||
Logger: log.New(log.DebugLevel),
|
||||
},
|
||||
args: args{
|
||||
useAuth: true,
|
||||
principal: &oauth2.Principal{
|
||||
Subject: "user",
|
||||
Issuer: "github",
|
||||
Organization: "default",
|
||||
},
|
||||
routerParams: &httprouter.Params{
|
||||
{
|
||||
Key: "oid",
|
||||
Value: "other",
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
matches: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing principal",
|
||||
fields: fields{
|
||||
Logger: log.New(log.DebugLevel),
|
||||
},
|
||||
args: args{
|
||||
useAuth: true,
|
||||
principal: nil,
|
||||
routerParams: &httprouter.Params{
|
||||
{
|
||||
Key: "oid",
|
||||
Value: "other",
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
matches: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not using auth",
|
||||
fields: fields{
|
||||
Logger: log.New(log.DebugLevel),
|
||||
},
|
||||
args: args{
|
||||
useAuth: false,
|
||||
principal: &oauth2.Principal{
|
||||
Subject: "user",
|
||||
Issuer: "github",
|
||||
Organization: "default",
|
||||
},
|
||||
routerParams: &httprouter.Params{
|
||||
{
|
||||
Key: "oid",
|
||||
Value: "other",
|
||||
},
|
||||
},
|
||||
},
|
||||
wants: wants{
|
||||
matches: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var matches bool
|
||||
next := func(w http.ResponseWriter, r *http.Request) {
|
||||
matches = true
|
||||
}
|
||||
fn := RouteMatchesPrincipal(
|
||||
tt.args.useAuth,
|
||||
tt.fields.Logger,
|
||||
next,
|
||||
)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
url := "http://any.url"
|
||||
r := httptest.NewRequest(
|
||||
"GET",
|
||||
url,
|
||||
nil,
|
||||
)
|
||||
if tt.args.routerParams != nil {
|
||||
r = r.WithContext(httprouter.WithParams(r.Context(), *tt.args.routerParams))
|
||||
}
|
||||
if tt.args.principal == nil {
|
||||
r = r.WithContext(context.WithValue(r.Context(), oauth2.PrincipalKey, nil))
|
||||
} else {
|
||||
r = r.WithContext(context.WithValue(r.Context(), oauth2.PrincipalKey, *tt.args.principal))
|
||||
}
|
||||
fn(w, r)
|
||||
|
||||
if matches != tt.wants.matches {
|
||||
t.Errorf("%q. RouteMatchesPrincipal() = %v, expected %v", tt.name, matches, tt.wants.matches)
|
||||
}
|
||||
|
||||
if !matches && w.Code != http.StatusForbidden {
|
||||
t.Errorf("%q. RouteMatchesPrincipal() Status Code = %v, expected %v", tt.name, w.Code, http.StatusForbidden)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
|
@ -68,6 +68,16 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
|
|||
hr.NotFound = http.StripPrefix(opts.Basepath, hr.NotFound)
|
||||
}
|
||||
|
||||
EnsureMember := func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return AuthorizedUser(
|
||||
service.Store,
|
||||
opts.UseAuth,
|
||||
roles.MemberRoleName,
|
||||
opts.Logger,
|
||||
next,
|
||||
)
|
||||
}
|
||||
_ = EnsureMember
|
||||
EnsureViewer := func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return AuthorizedUser(
|
||||
service.Store,
|
||||
|
@ -105,8 +115,16 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
|
|||
)
|
||||
}
|
||||
|
||||
checkForRawQuery := func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return CheckForRawQuery(opts.Logger, next)
|
||||
rawStoreAccess := func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return RawStoreAccess(opts.Logger, next)
|
||||
}
|
||||
|
||||
ensureOrgMatches := func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return RouteMatchesPrincipal(
|
||||
opts.UseAuth,
|
||||
opts.Logger,
|
||||
next,
|
||||
)
|
||||
}
|
||||
|
||||
/* Documentation */
|
||||
|
@ -118,9 +136,9 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
|
|||
router.GET("/chronograf/v1/organizations", EnsureAdmin(service.Organizations))
|
||||
router.POST("/chronograf/v1/organizations", EnsureSuperAdmin(service.NewOrganization))
|
||||
|
||||
router.GET("/chronograf/v1/organizations/:id", EnsureAdmin(service.OrganizationID))
|
||||
router.PATCH("/chronograf/v1/organizations/:id", EnsureSuperAdmin(service.UpdateOrganization))
|
||||
router.DELETE("/chronograf/v1/organizations/:id", EnsureSuperAdmin(service.RemoveOrganization))
|
||||
router.GET("/chronograf/v1/organizations/:oid", EnsureAdmin(service.OrganizationID))
|
||||
router.PATCH("/chronograf/v1/organizations/:oid", EnsureSuperAdmin(service.UpdateOrganization))
|
||||
router.DELETE("/chronograf/v1/organizations/:oid", EnsureSuperAdmin(service.RemoveOrganization))
|
||||
|
||||
// Sources
|
||||
router.GET("/chronograf/v1/sources", EnsureViewer(service.Sources))
|
||||
|
@ -198,12 +216,19 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
|
|||
router.PUT("/chronograf/v1/me", service.UpdateMe(opts.Auth))
|
||||
|
||||
// TODO(desa): what to do about admin's being able to set superadmin
|
||||
router.GET("/chronograf/v1/users", EnsureAdmin(checkForRawQuery(service.Users)))
|
||||
router.POST("/chronograf/v1/users", EnsureAdmin(checkForRawQuery(service.NewUser)))
|
||||
router.GET("/chronograf/v1/organizations/:oid/users", EnsureAdmin(ensureOrgMatches(service.Users)))
|
||||
router.POST("/chronograf/v1/organizations/:oid/users", EnsureAdmin(ensureOrgMatches(service.NewUser)))
|
||||
|
||||
router.GET("/chronograf/v1/users/:id", EnsureAdmin(checkForRawQuery(service.UserID)))
|
||||
router.DELETE("/chronograf/v1/users/:id", EnsureAdmin(checkForRawQuery(service.RemoveUser)))
|
||||
router.PATCH("/chronograf/v1/users/:id", EnsureAdmin(checkForRawQuery(service.UpdateUser)))
|
||||
router.GET("/chronograf/v1/organizations/:oid/users/:id", EnsureAdmin(ensureOrgMatches(service.UserID)))
|
||||
router.DELETE("/chronograf/v1/organizations/:oid/users/:id", EnsureAdmin(ensureOrgMatches(service.RemoveUser)))
|
||||
router.PATCH("/chronograf/v1/organizations/:oid/users/:id", EnsureAdmin(ensureOrgMatches(service.UpdateUser)))
|
||||
|
||||
router.GET("/chronograf/v1/users", EnsureSuperAdmin(rawStoreAccess(service.Users)))
|
||||
router.POST("/chronograf/v1/users", EnsureSuperAdmin(rawStoreAccess(service.NewUser)))
|
||||
|
||||
router.GET("/chronograf/v1/users/:id", EnsureSuperAdmin(rawStoreAccess(service.UserID)))
|
||||
router.DELETE("/chronograf/v1/users/:id", EnsureSuperAdmin(rawStoreAccess(service.RemoveUser)))
|
||||
router.PATCH("/chronograf/v1/users/:id", EnsureSuperAdmin(rawStoreAccess(service.UpdateUser)))
|
||||
|
||||
// Dashboards
|
||||
router.GET("/chronograf/v1/dashboards", EnsureViewer(service.Dashboards))
|
||||
|
|
|
@ -165,7 +165,7 @@ func (s *Service) NewOrganization(w http.ResponseWriter, r *http.Request) {
|
|||
func (s *Service) OrganizationID(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
id := httprouter.GetParamFromContext(ctx, "id")
|
||||
id := httprouter.GetParamFromContext(ctx, "oid")
|
||||
|
||||
org, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &id})
|
||||
if err != nil {
|
||||
|
@ -191,7 +191,7 @@ func (s *Service) UpdateOrganization(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
ctx := r.Context()
|
||||
id := httprouter.GetParamFromContext(ctx, "id")
|
||||
id := httprouter.GetParamFromContext(ctx, "oid")
|
||||
|
||||
org, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &id})
|
||||
if err != nil {
|
||||
|
@ -226,7 +226,7 @@ func (s *Service) UpdateOrganization(w http.ResponseWriter, r *http.Request) {
|
|||
// RemoveOrganization removes an organization in the organizations store
|
||||
func (s *Service) RemoveOrganization(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
id := httprouter.GetParamFromContext(ctx, "id")
|
||||
id := httprouter.GetParamFromContext(ctx, "oid")
|
||||
|
||||
org, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &id})
|
||||
if err != nil {
|
||||
|
|
|
@ -82,7 +82,7 @@ func TestService_OrganizationID(t *testing.T) {
|
|||
context.Background(),
|
||||
httprouter.Params{
|
||||
{
|
||||
Key: "id",
|
||||
Key: "oid",
|
||||
Value: tt.id,
|
||||
},
|
||||
}))
|
||||
|
@ -411,7 +411,7 @@ func TestService_UpdateOrganization(t *testing.T) {
|
|||
tt.args.r = tt.args.r.WithContext(httprouter.WithParams(context.Background(),
|
||||
httprouter.Params{
|
||||
{
|
||||
Key: "id",
|
||||
Key: "oid",
|
||||
Value: tt.id,
|
||||
},
|
||||
}))
|
||||
|
@ -503,7 +503,7 @@ func TestService_RemoveOrganization(t *testing.T) {
|
|||
tt.args.r = tt.args.r.WithContext(httprouter.WithParams(context.Background(),
|
||||
httprouter.Params{
|
||||
{
|
||||
Key: "id",
|
||||
Key: "oid",
|
||||
Value: tt.id,
|
||||
},
|
||||
}))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
|
@ -31,7 +32,7 @@ func (r *AuthRoutes) Lookup(provider string) (AuthRoute, bool) {
|
|||
type getRoutesResponse struct {
|
||||
Layouts string `json:"layouts"` // Location of the layouts endpoint
|
||||
Users string `json:"users"` // Location of the users endpoint
|
||||
RawUsers string `json:"rawUsers"` // Location of the raw users endpoint
|
||||
AllUsers string `json:"allUsers"` // Location of the raw users endpoint
|
||||
Organizations string `json:"organizations"` // Location of the organizations endpoint
|
||||
Mappings string `json:"mappings"` // Location of the application mappings endpoint
|
||||
Sources string `json:"sources"` // Location of the sources endpoint
|
||||
|
@ -48,6 +49,7 @@ type getRoutesResponse struct {
|
|||
// external links for the client to know about, such as for JSON feeds or custom side nav buttons.
|
||||
// Optionally, routes for authentication can be returned.
|
||||
type AllRoutes struct {
|
||||
Middleware func(http.HandlerFunc) http.HandlerFunc
|
||||
AuthRoutes []AuthRoute // Location of all auth routes. If no auth, this can be empty.
|
||||
LogoutLink string // Location of the logout route for all auth routes. If no auth, this can be empty.
|
||||
StatusFeed string // External link to the JSON Feed for the News Feed on the client's Status Page
|
||||
|
@ -56,18 +58,32 @@ type AllRoutes struct {
|
|||
}
|
||||
|
||||
// ServeHTTP returns all top level routes and external links within chronograf
|
||||
func (a *AllRoutes) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *AllRoutes) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if s.Middleware != nil {
|
||||
s.Middleware(s.serveHTTP)(w, r)
|
||||
return
|
||||
}
|
||||
s.serveHTTP(w, r)
|
||||
}
|
||||
|
||||
// serveHTTP returns all top level routes and external links within chronograf
|
||||
func (a *AllRoutes) serveHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
customLinks, err := NewCustomLinks(a.CustomLinks)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, err.Error(), a.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
org := "default"
|
||||
if contextOrg, ok := hasOrganizationContext(r.Context()); ok {
|
||||
org = contextOrg
|
||||
}
|
||||
|
||||
routes := getRoutesResponse{
|
||||
Sources: "/chronograf/v1/sources",
|
||||
Layouts: "/chronograf/v1/layouts",
|
||||
Users: "/chronograf/v1/users",
|
||||
RawUsers: "/chronograf/v1/users?raw=true",
|
||||
Users: fmt.Sprintf("/chronograf/v1/organizations/%s/users", org),
|
||||
AllUsers: "/chronograf/v1/users",
|
||||
Organizations: "/chronograf/v1/organizations",
|
||||
Me: "/chronograf/v1/me",
|
||||
Environment: "/chronograf/v1/env",
|
||||
|
|
|
@ -29,7 +29,7 @@ func TestAllRoutes(t *testing.T) {
|
|||
if err := json.Unmarshal(body, &routes); err != nil {
|
||||
t.Error("TestAllRoutes not able to unmarshal JSON response")
|
||||
}
|
||||
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/users","rawUsers":"/chronograf/v1/users?raw=true","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":""}}
|
||||
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":""}}
|
||||
`
|
||||
if want != string(body) {
|
||||
t.Errorf("TestAllRoutes\nwanted\n*%s*\ngot\n*%s*", want, string(body))
|
||||
|
@ -67,7 +67,7 @@ func TestAllRoutesWithAuth(t *testing.T) {
|
|||
if err := json.Unmarshal(body, &routes); err != nil {
|
||||
t.Error("TestAllRoutesWithAuth not able to unmarshal JSON response")
|
||||
}
|
||||
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/users","rawUsers":"/chronograf/v1/users?raw=true","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[{"name":"github","label":"GitHub","login":"/oauth/github/login","logout":"/oauth/github/logout","callback":"/oauth/github/callback"}],"logout":"/oauth/logout","external":{"statusFeed":""}}
|
||||
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[{"name":"github","label":"GitHub","login":"/oauth/github/login","logout":"/oauth/github/logout","callback":"/oauth/github/callback"}],"logout":"/oauth/logout","external":{"statusFeed":""}}
|
||||
`
|
||||
if want != string(body) {
|
||||
t.Errorf("TestAllRoutesWithAuth\nwanted\n*%s*\ngot\n*%s*", want, string(body))
|
||||
|
@ -100,7 +100,7 @@ func TestAllRoutesWithExternalLinks(t *testing.T) {
|
|||
if err := json.Unmarshal(body, &routes); err != nil {
|
||||
t.Error("TestAllRoutesWithExternalLinks not able to unmarshal JSON response")
|
||||
}
|
||||
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/users","rawUsers":"/chronograf/v1/users?raw=true","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":"http://pineapple.life/feed.json","custom":[{"name":"cubeapple","url":"https://cube.apple"}]}}
|
||||
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":"http://pineapple.life/feed.json","custom":[{"name":"cubeapple","url":"https://cube.apple"}]}}
|
||||
`
|
||||
if want != string(body) {
|
||||
t.Errorf("TestAllRoutesWithExternalLinks\nwanted\n*%s*\ngot\n*%s*", want, string(body))
|
||||
|
|
|
@ -79,16 +79,18 @@ type userResponse struct {
|
|||
Roles []chronograf.Role `json:"roles"`
|
||||
}
|
||||
|
||||
func newUserResponse(u *chronograf.User, raw bool) *userResponse {
|
||||
func newUserResponse(u *chronograf.User, org string) *userResponse {
|
||||
// This ensures that any user response with no roles returns an empty array instead of
|
||||
// null when marshaled into JSON. That way, JavaScript doesn't need any guard on the
|
||||
// key existing and it can simply be iterated over.
|
||||
if u.Roles == nil {
|
||||
u.Roles = []chronograf.Role{}
|
||||
}
|
||||
selfLink := fmt.Sprintf("/chronograf/v1/users/%d", u.ID)
|
||||
if raw {
|
||||
selfLink = fmt.Sprintf("%s?raw=true", selfLink)
|
||||
var selfLink string
|
||||
if org != "" {
|
||||
selfLink = fmt.Sprintf("/chronograf/v1/organizations/%s/users/%d", org, u.ID)
|
||||
} else {
|
||||
selfLink = fmt.Sprintf("/chronograf/v1/users/%d", u.ID)
|
||||
}
|
||||
return &userResponse{
|
||||
ID: u.ID,
|
||||
|
@ -108,18 +110,20 @@ type usersResponse struct {
|
|||
Users []*userResponse `json:"users"`
|
||||
}
|
||||
|
||||
func newUsersResponse(users []chronograf.User, raw bool) *usersResponse {
|
||||
func newUsersResponse(users []chronograf.User, org string) *usersResponse {
|
||||
usersResp := make([]*userResponse, len(users))
|
||||
for i, user := range users {
|
||||
usersResp[i] = newUserResponse(&user, raw)
|
||||
usersResp[i] = newUserResponse(&user, org)
|
||||
}
|
||||
sort.Slice(usersResp, func(i, j int) bool {
|
||||
return usersResp[i].ID < usersResp[j].ID
|
||||
})
|
||||
|
||||
selfLink := "/chronograf/v1/users"
|
||||
if raw {
|
||||
selfLink = fmt.Sprintf("%s?raw=true", selfLink)
|
||||
var selfLink string
|
||||
if org != "" {
|
||||
selfLink = fmt.Sprintf("/chronograf/v1/organizations/%s/users", org)
|
||||
} else {
|
||||
selfLink = "/chronograf/v1/users"
|
||||
}
|
||||
return &usersResponse{
|
||||
Users: usersResp,
|
||||
|
@ -145,7 +149,9 @@ func (s *Service) UserID(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
res := newUserResponse(user, hasServerContext(ctx))
|
||||
orgID := httprouter.GetParamFromContext(ctx, "oid")
|
||||
res := newUserResponse(user, orgID)
|
||||
location(w, res.Links.Self)
|
||||
encodeJSON(w, http.StatusOK, res, s.Logger)
|
||||
}
|
||||
|
||||
|
@ -198,7 +204,8 @@ func (s *Service) NewUser(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
cu := newUserResponse(res, hasServerContext(ctx))
|
||||
orgID := httprouter.GetParamFromContext(ctx, "oid")
|
||||
cu := newUserResponse(res, orgID)
|
||||
location(w, cu.Links.Self)
|
||||
encodeJSON(w, http.StatusCreated, cu, s.Logger)
|
||||
}
|
||||
|
@ -319,7 +326,8 @@ func (s *Service) UpdateUser(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
cu := newUserResponse(u, hasServerContext(ctx))
|
||||
orgID := httprouter.GetParamFromContext(ctx, "oid")
|
||||
cu := newUserResponse(u, orgID)
|
||||
location(w, cu.Links.Self)
|
||||
encodeJSON(w, http.StatusOK, cu, s.Logger)
|
||||
}
|
||||
|
@ -334,7 +342,8 @@ func (s *Service) Users(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
res := newUsersResponse(users, hasServerContext(ctx))
|
||||
orgID := httprouter.GetParamFromContext(ctx, "oid")
|
||||
res := newUsersResponse(users, orgID)
|
||||
encodeJSON(w, http.StatusOK, res, s.Logger)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue