package auth
import (
"fmt"
"strconv"
"testing"
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/auth/user"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/kubernetes/pkg/util/sets"
authorizationapi "github.com/openshift/origin/pkg/authorization/api"
"github.com/openshift/origin/pkg/client"
)
// MockPolicyClient implements the PolicyCache interface for testing
type MockPolicyClient struct{}
// Following methods enable the MockPolicyClient to implement the PolicyCache interface
// Policies gives access to a read-only policy interface
func (this *MockPolicyClient) Policies(namespace string) client.PolicyLister {
return MockPolicyGetter{}
}
type MockPolicyGetter struct{}
func (this MockPolicyGetter) List(options kapi.ListOptions) (*authorizationapi.PolicyList, error) {
return &authorizationapi.PolicyList{}, nil
}
func (this MockPolicyGetter) Get(name string) (*authorizationapi.Policy, error) {
return &authorizationapi.Policy{}, nil
}
// ClusterPolicies gives access to a read-only cluster policy interface
func (this *MockPolicyClient) ClusterPolicies() client.ClusterPolicyLister {
return MockClusterPolicyGetter{}
}
type MockClusterPolicyGetter struct{}
func (this MockClusterPolicyGetter) List(options kapi.ListOptions) (*authorizationapi.ClusterPolicyList, error) {
return &authorizationapi.ClusterPolicyList{}, nil
}
func (this MockClusterPolicyGetter) Get(name string) (*authorizationapi.ClusterPolicy, error) {
return &authorizationapi.ClusterPolicy{}, nil
}
// PolicyBindings gives access to a read-only policy binding interface
func (this *MockPolicyClient) PolicyBindings(namespace string) client.PolicyBindingLister {
return MockPolicyBindingGetter{}
}
type MockPolicyBindingGetter struct{}
func (this MockPolicyBindingGetter) List(options kapi.ListOptions) (*authorizationapi.PolicyBindingList, error) {
return &authorizationapi.PolicyBindingList{}, nil
}
func (this MockPolicyBindingGetter) Get(name string) (*authorizationapi.PolicyBinding, error) {
return &authorizationapi.PolicyBinding{}, nil
}
// ClusterPolicyBindings gives access to a read-only cluster policy binding interface
func (this *MockPolicyClient) ClusterPolicyBindings() client.ClusterPolicyBindingLister {
return MockClusterPolicyBindingGetter{}
}
type MockClusterPolicyBindingGetter struct{}
func (this MockClusterPolicyBindingGetter) List(options kapi.ListOptions) (*authorizationapi.ClusterPolicyBindingList, error) {
return &authorizationapi.ClusterPolicyBindingList{}, nil
}
func (this MockClusterPolicyBindingGetter) Get(name string) (*authorizationapi.ClusterPolicyBinding, error) {
return &authorizationapi.ClusterPolicyBinding{}, nil
}
// LastSyncResourceVersion returns the resource version for the last sync performed
func (this *MockPolicyClient) LastSyncResourceVersion() string {
return ""
}
// mockReview implements the Review interface for test cases
type mockReview struct {
users []string
groups []string
err string
}
// Users returns the users that can access a resource
func (r *mockReview) Users() []string {
return r.users
}
// Groups returns the groups that can access a resource
func (r *mockReview) Groups() []string {
return r.groups
}
func (r *mockReview) EvaluationError() string {
return r.err
}
// common test users
var (
alice = &user.DefaultInfo{
Name: "Alice",
UID: "alice-uid",
Groups: []string{},
}
bob = &user.DefaultInfo{
Name: "Bob",
UID: "bob-uid",
Groups: []string{"employee"},
}
eve = &user.DefaultInfo{
Name: "Eve",
UID: "eve-uid",
Groups: []string{"employee"},
}
frank = &user.DefaultInfo{
Name: "Frank",
UID: "frank-uid",
Groups: []string{},
}
)
// mockReviewer returns the specified values for each supplied resource
type mockReviewer struct {
expectedResults map[string]*mockReview
}
// Review returns the mapped review from the mock object, or an error if none exists
func (mr *mockReviewer) Review(name string) (Review, error) {
review := mr.expectedResults[name]
if review == nil {
return nil, fmt.Errorf("Item %s does not exist", name)
}
return review, nil
}
func validateList(t *testing.T, lister Lister, user user.Info, expectedSet sets.String) {
namespaceList, err := lister.List(user)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
results := sets.String{}
for _, namespace := range namespaceList.Items {
results.Insert(namespace.Name)
}
if results.Len() != expectedSet.Len() || !results.HasAll(expectedSet.List()...) {
t.Errorf("User %v, Expected: %v, Actual: %v", user.GetName(), expectedSet, results)
}
}
func TestSyncNamespace(t *testing.T) {
namespaceList := kapi.NamespaceList{
Items: []kapi.Namespace{
{
ObjectMeta: kapi.ObjectMeta{Name: "foo", ResourceVersion: "1"},
},
{
ObjectMeta: kapi.ObjectMeta{Name: "bar", ResourceVersion: "2"},
},
{
ObjectMeta: kapi.ObjectMeta{Name: "car", ResourceVersion: "3"},
},
},
}
mockKubeClient := fake.NewSimpleClientset(&namespaceList)
reviewer := &mockReviewer{
expectedResults: map[string]*mockReview{
"foo": {
users: []string{alice.GetName(), bob.GetName()},
groups: eve.GetGroups(),
},
"bar": {
users: []string{frank.GetName(), eve.GetName()},
groups: []string{"random"},
},
"car": {
users: []string{},
groups: []string{},
},
},
}
mockPolicyCache := &MockPolicyClient{}
authorizationCache := NewAuthorizationCache(reviewer, mockKubeClient.Core().Namespaces(), mockPolicyCache, mockPolicyCache, mockPolicyCache, mockPolicyCache)
// we prime the data we need here since we are not running reflectors
for i := range namespaceList.Items {
authorizationCache.namespaceStore.Add(&namespaceList.Items[i])
}
// synchronize the cache
authorizationCache.synchronize()
validateList(t, authorizationCache, alice, sets.NewString("foo"))
validateList(t, authorizationCache, bob, sets.NewString("foo"))
validateList(t, authorizationCache, eve, sets.NewString("foo", "bar"))
validateList(t, authorizationCache, frank, sets.NewString("bar"))
// modify access rules
reviewer.expectedResults["foo"].users = []string{bob.GetName()}
reviewer.expectedResults["foo"].groups = []string{"random"}
reviewer.expectedResults["bar"].users = []string{alice.GetName(), eve.GetName()}
reviewer.expectedResults["bar"].groups = []string{"employee"}
reviewer.expectedResults["car"].users = []string{bob.GetName(), eve.GetName()}
reviewer.expectedResults["car"].groups = []string{"employee"}
// modify resource version on each namespace to simulate a change had occurred to force cache refresh
for i := range namespaceList.Items {
namespace := namespaceList.Items[i]
oldVersion, err := strconv.Atoi(namespace.ResourceVersion)
if err != nil {
t.Errorf("Bad test setup, resource versions should be numbered, %v", err)
}
newVersion := strconv.Itoa(oldVersion + 1)
namespace.ResourceVersion = newVersion
authorizationCache.namespaceStore.Add(&namespace)
}
// now refresh the cache (which is resource version aware)
authorizationCache.synchronize()
// make sure new rights hold
validateList(t, authorizationCache, alice, sets.NewString("bar"))
validateList(t, authorizationCache, bob, sets.NewString("foo", "bar", "car"))
validateList(t, authorizationCache, eve, sets.NewString("bar", "car"))
validateList(t, authorizationCache, frank, sets.NewString())
}