mirror of
https://github.com/grafana/grafana.git
synced 2024-12-01 04:59:15 -06:00
8bcd9c2594
* Refactor identity struct to store type in separate field * Update ResolveIdentity to take string representation of typedID * Add IsIdentityType to requester interface * Use IsIdentityType from interface * Remove usage of TypedID * Remote typedID struct * fix GetInternalID
242 lines
6.3 KiB
Go
242 lines
6.3 KiB
Go
package clients
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/authlib/claims"
|
|
"github.com/grafana/grafana/pkg/services/authn"
|
|
"github.com/grafana/grafana/pkg/services/authn/authntest"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
)
|
|
|
|
func TestProxy_Authenticate(t *testing.T) {
|
|
type testCase struct {
|
|
desc string
|
|
req *authn.Request
|
|
ips string
|
|
proxyHeader string
|
|
proxyHeaders map[string]string
|
|
expectedErr error
|
|
expectedUsername string
|
|
expectedAdditional map[string]string
|
|
}
|
|
|
|
tests := []testCase{
|
|
{
|
|
desc: "should authenticate using passed in proxy client",
|
|
ips: "127.0.0.1",
|
|
req: &authn.Request{
|
|
HTTPRequest: &http.Request{
|
|
Header: map[string][]string{
|
|
"X-Username": {"username"},
|
|
"X-Name": {"name"},
|
|
"X-Email": {"email"},
|
|
"X-Login": {"login"},
|
|
"X-Role": {"Viewer"},
|
|
"X-Group": {"grp1,grp2"},
|
|
},
|
|
RemoteAddr: "127.0.0.1:333",
|
|
},
|
|
},
|
|
proxyHeader: "X-Username",
|
|
proxyHeaders: map[string]string{
|
|
proxyFieldName: "X-Name",
|
|
proxyFieldEmail: "X-Email",
|
|
proxyFieldLogin: "X-Login",
|
|
proxyFieldRole: "X-Role",
|
|
proxyFieldGroups: "X-Group",
|
|
},
|
|
expectedUsername: "username",
|
|
expectedAdditional: map[string]string{
|
|
proxyFieldName: "name",
|
|
proxyFieldEmail: "email",
|
|
proxyFieldLogin: "login",
|
|
proxyFieldRole: "Viewer",
|
|
proxyFieldGroups: "grp1,grp2",
|
|
},
|
|
},
|
|
{
|
|
desc: "should fail when proxy header is empty",
|
|
req: &authn.Request{
|
|
HTTPRequest: &http.Request{Header: map[string][]string{
|
|
"X-Username": {""},
|
|
"X-Name": {"name"},
|
|
"X-Email": {"email"},
|
|
"X-Login": {"login"},
|
|
"X-Role": {"Viewer"},
|
|
"X-Group": {"grp1,grp2"},
|
|
}},
|
|
},
|
|
proxyHeader: "X-Username",
|
|
proxyHeaders: map[string]string{
|
|
proxyFieldName: "X-Name",
|
|
proxyFieldEmail: "X-Email",
|
|
proxyFieldLogin: "X-Login",
|
|
proxyFieldRole: "X-Role",
|
|
proxyFieldGroups: "X-Group",
|
|
},
|
|
expectedErr: errEmptyProxyHeader,
|
|
},
|
|
{
|
|
desc: "should fail when caller ip is not in accept list",
|
|
req: &authn.Request{
|
|
HTTPRequest: &http.Request{
|
|
Header: map[string][]string{},
|
|
RemoteAddr: "127.0.0.2:333",
|
|
},
|
|
},
|
|
ips: "127.0.0.1",
|
|
expectedErr: errNotAcceptedIP,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.desc, func(t *testing.T) {
|
|
cfg := setting.NewCfg()
|
|
cfg.AuthProxy.HeaderName = "X-Username"
|
|
cfg.AuthProxy.Headers = tt.proxyHeaders
|
|
cfg.AuthProxy.Whitelist = tt.ips
|
|
|
|
calledUsername := ""
|
|
var calledAdditional map[string]string
|
|
|
|
proxyClient := authntest.MockProxyClient{AuthenticateProxyFunc: func(ctx context.Context, r *authn.Request, username string, additional map[string]string) (*authn.Identity, error) {
|
|
calledUsername = username
|
|
calledAdditional = additional
|
|
return nil, nil
|
|
}}
|
|
c, err := ProvideProxy(cfg, &fakeCache{expectedErr: errors.New("")}, proxyClient)
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Authenticate(context.Background(), tt.req)
|
|
assert.ErrorIs(t, err, tt.expectedErr)
|
|
assert.Equal(t, tt.expectedUsername, calledUsername)
|
|
assert.EqualValues(t, tt.expectedAdditional, calledAdditional)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestProxy_Test(t *testing.T) {
|
|
type testCase struct {
|
|
desc string
|
|
req *authn.Request
|
|
expectedOK bool
|
|
}
|
|
|
|
tests := []testCase{
|
|
{
|
|
desc: "should return true when proxy header exists",
|
|
req: &authn.Request{
|
|
HTTPRequest: &http.Request{
|
|
Header: map[string][]string{"Proxy-Header": {"some value"}},
|
|
},
|
|
},
|
|
expectedOK: true,
|
|
},
|
|
{
|
|
desc: "should return false when proxy header exists but has no value",
|
|
req: &authn.Request{
|
|
HTTPRequest: &http.Request{
|
|
Header: map[string][]string{"Proxy-Header": {""}},
|
|
},
|
|
},
|
|
expectedOK: false,
|
|
},
|
|
{
|
|
desc: "should return false when no proxy header is set on request",
|
|
req: &authn.Request{
|
|
HTTPRequest: &http.Request{Header: map[string][]string{}},
|
|
},
|
|
expectedOK: false,
|
|
},
|
|
{
|
|
desc: "should return false when no http request is present",
|
|
req: &authn.Request{},
|
|
expectedOK: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.desc, func(t *testing.T) {
|
|
cfg := setting.NewCfg()
|
|
cfg.AuthProxy.HeaderName = "Proxy-Header"
|
|
|
|
c, _ := ProvideProxy(cfg, nil, nil, nil)
|
|
assert.Equal(t, tt.expectedOK, c.Test(context.Background(), tt.req))
|
|
})
|
|
}
|
|
}
|
|
|
|
var _ proxyCache = new(fakeCache)
|
|
|
|
type fakeCache struct {
|
|
data map[string][]byte
|
|
expectedErr error
|
|
}
|
|
|
|
func (f *fakeCache) Get(ctx context.Context, key string) ([]byte, error) {
|
|
return f.data[key], f.expectedErr
|
|
}
|
|
|
|
func (f *fakeCache) Set(ctx context.Context, key string, value []byte, expire time.Duration) error {
|
|
f.data[key] = value
|
|
return f.expectedErr
|
|
}
|
|
|
|
func (f fakeCache) Delete(ctx context.Context, key string) error {
|
|
delete(f.data, key)
|
|
return f.expectedErr
|
|
}
|
|
|
|
func TestProxy_Hook(t *testing.T) {
|
|
cfg := setting.NewCfg()
|
|
cfg.AuthProxy.HeaderName = "X-Username"
|
|
cfg.AuthProxy.Headers = map[string]string{
|
|
proxyFieldRole: "X-Role",
|
|
}
|
|
cache := &fakeCache{data: make(map[string][]byte)}
|
|
|
|
// withRole creates a test case for a user with a specific role.
|
|
withRole := func(role string) func(t *testing.T) {
|
|
cacheKey := fmt.Sprintf("users:johndoe-%s", role)
|
|
return func(t *testing.T) {
|
|
c, err := ProvideProxy(cfg, cache, authntest.MockProxyClient{})
|
|
require.NoError(t, err)
|
|
userIdentity := &authn.Identity{
|
|
ID: "1",
|
|
Type: claims.TypeUser,
|
|
ClientParams: authn.ClientParams{
|
|
CacheAuthProxyKey: cacheKey,
|
|
},
|
|
}
|
|
userReq := &authn.Request{
|
|
HTTPRequest: &http.Request{
|
|
Header: map[string][]string{
|
|
"X-Username": {"johndoe"},
|
|
"X-Role": {role},
|
|
},
|
|
},
|
|
}
|
|
err = c.Hook(context.Background(), userIdentity, userReq)
|
|
assert.NoError(t, err)
|
|
expectedCache := map[string][]byte{
|
|
cacheKey: []byte("1"),
|
|
fmt.Sprintf("%s:%s", proxyCachePrefix, "johndoe"): []byte(fmt.Sprintf("users:johndoe-%s", role)),
|
|
}
|
|
assert.Equal(t, expectedCache, cache.data)
|
|
}
|
|
}
|
|
|
|
t.Run("step 1: new user with role Admin", withRole("Admin"))
|
|
t.Run("step 2: cached user with new Role Viewer", withRole("Viewer"))
|
|
t.Run("step 3: cached user get changed back to Admin", withRole("Admin"))
|
|
}
|