Add AuthorizedUser middleware
parent
f0f5bc071b
commit
28fac10baa
|
@ -2,6 +2,7 @@ package server
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
|
@ -45,3 +46,95 @@ func AuthorizedToken(auth oauth2.Authenticator, logger chronograf.Logger, next h
|
|||
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"
|
||||
"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 +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