Add AuthorizedUser middleware
parent
f0f5bc071b
commit
28fac10baa
|
@ -2,6 +2,7 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/influxdata/chronograf"
|
"github.com/influxdata/chronograf"
|
||||||
|
@ -45,3 +46,95 @@ func AuthorizedToken(auth oauth2.Authenticator, logger chronograf.Logger, next h
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, fmt.Sprintf("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, fmt.Sprintf("User %s is not authorized", username), logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := getUserBy(store, ctx, username, provider)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error to retrieving user")
|
||||||
|
Error(w, http.StatusUnauthorized, fmt.Sprintf("User %s is not authorized", username), logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if u == nil {
|
||||||
|
log.Error("User not found")
|
||||||
|
Error(w, http.StatusNotFound, fmt.Sprintf("User with name %s and provider %s not found", username, provider), logger)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasPrivelege(u, role) {
|
||||||
|
next(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Error(w, http.StatusUnauthorized, fmt.Sprintf("User %s is not authorized", username), logger)
|
||||||
|
return
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasPrivelege(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
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case EditorRoleName:
|
||||||
|
for _, r := range u.Roles {
|
||||||
|
switch r.Name {
|
||||||
|
case EditorRoleName, AdminRoleName:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case AdminRoleName:
|
||||||
|
for _, r := range u.Roles {
|
||||||
|
switch r.Name {
|
||||||
|
case AdminRoleName:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,9 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/influxdata/chronograf"
|
||||||
clog "github.com/influxdata/chronograf/log"
|
clog "github.com/influxdata/chronograf/log"
|
||||||
|
"github.com/influxdata/chronograf/mocks"
|
||||||
"github.com/influxdata/chronograf/oauth2"
|
"github.com/influxdata/chronograf/oauth2"
|
||||||
"github.com/influxdata/chronograf/server"
|
"github.com/influxdata/chronograf/server"
|
||||||
)
|
)
|
||||||
|
@ -94,3 +96,481 @@ 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{
|
||||||
|
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||||
|
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{
|
||||||
|
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||||
|
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{
|
||||||
|
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||||
|
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{
|
||||||
|
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||||
|
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{
|
||||||
|
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||||
|
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{
|
||||||
|
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||||
|
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{
|
||||||
|
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||||
|
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{
|
||||||
|
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||||
|
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{
|
||||||
|
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||||
|
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{
|
||||||
|
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||||
|
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{
|
||||||
|
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||||
|
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{
|
||||||
|
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||||
|
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{
|
||||||
|
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||||
|
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{
|
||||||
|
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||||
|
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{
|
||||||
|
AllF: func(ctx context.Context) ([]chronograf.User, error) {
|
||||||
|
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, tt.authorized, authorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue