diff --git a/plugin/pkg/auth/authenticator/request/union/union.go b/plugin/pkg/auth/authenticator/request/union/union.go new file mode 100644 index 00000000000..4b18ee5055b --- /dev/null +++ b/plugin/pkg/auth/authenticator/request/union/union.go @@ -0,0 +1,52 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package union + +import ( + "net/http" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator" + "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" +) + +// unionAuthRequestHandler authenticates requests using a chain of authenticator.Requests +type unionAuthRequestHandler []authenticator.Request + +// New returns a request authenticator that validates credentials using a chain of authenticator.Request objects +func New(authRequestHandlers ...authenticator.Request) authenticator.Request { + return unionAuthRequestHandler(authRequestHandlers) +} + +// AuthenticateRequest authenticates the request using a chain of authenticator.Request objects. The first +// success returns that identity. Errors are only returned if no matches are found. +func (authHandler unionAuthRequestHandler) AuthenticateRequest(req *http.Request) (user.Info, bool, error) { + var errors util.ErrorList + for _, currAuthRequestHandler := range authHandler { + info, ok, err := currAuthRequestHandler.AuthenticateRequest(req) + if err != nil { + errors = append(errors, err) + continue + } + + if ok { + return info, true, nil + } + } + + return nil, false, errors.ToError() +} diff --git a/plugin/pkg/auth/authenticator/request/union/unionauth_test.go b/plugin/pkg/auth/authenticator/request/union/unionauth_test.go new file mode 100644 index 00000000000..90bc064700b --- /dev/null +++ b/plugin/pkg/auth/authenticator/request/union/unionauth_test.go @@ -0,0 +1,145 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package union + +import ( + "errors" + "net/http" + "reflect" + "strings" + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user" +) + +type mockAuthRequestHandler struct { + returnUser user.Info + isAuthenticated bool + err error +} + +var ( + user1 = &user.DefaultInfo{Name: "fresh_ferret", UID: "alfa"} + user2 = &user.DefaultInfo{Name: "elegant_sheep", UID: "bravo"} +) + +func (mock *mockAuthRequestHandler) AuthenticateRequest(req *http.Request) (user.Info, bool, error) { + return mock.returnUser, mock.isAuthenticated, mock.err +} + +func TestAuthenticateRequestSecondPasses(t *testing.T) { + handler1 := &mockAuthRequestHandler{returnUser: user1} + handler2 := &mockAuthRequestHandler{returnUser: user2, isAuthenticated: true} + authRequestHandler := New(handler1, handler2) + req, _ := http.NewRequest("GET", "http://example.org", nil) + + authenticatedUser, isAuthenticated, err := authRequestHandler.AuthenticateRequest(req) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !isAuthenticated { + t.Errorf("Unexpectedly unauthenticated: %v", isAuthenticated) + } + if !reflect.DeepEqual(user2, authenticatedUser) { + t.Errorf("Expected %v, got %v", user2, authenticatedUser) + } +} + +func TestAuthenticateRequestFirstPasses(t *testing.T) { + handler1 := &mockAuthRequestHandler{returnUser: user1, isAuthenticated: true} + handler2 := &mockAuthRequestHandler{returnUser: user2} + authRequestHandler := New(handler1, handler2) + req, _ := http.NewRequest("GET", "http://example.org", nil) + + authenticatedUser, isAuthenticated, err := authRequestHandler.AuthenticateRequest(req) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !isAuthenticated { + t.Errorf("Unexpectedly unauthenticated: %v", isAuthenticated) + } + if !reflect.DeepEqual(user1, authenticatedUser) { + t.Errorf("Expected %v, got %v", user1, authenticatedUser) + } +} + +func TestAuthenticateRequestSuppressUnnecessaryErrors(t *testing.T) { + handler1 := &mockAuthRequestHandler{err: errors.New("first")} + handler2 := &mockAuthRequestHandler{isAuthenticated: true} + authRequestHandler := New(handler1, handler2) + req, _ := http.NewRequest("GET", "http://example.org", nil) + + _, isAuthenticated, err := authRequestHandler.AuthenticateRequest(req) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !isAuthenticated { + t.Errorf("Unexpectedly unauthenticated: %v", isAuthenticated) + } +} + +func TestAuthenticateRequestNoAuthenticators(t *testing.T) { + authRequestHandler := New() + req, _ := http.NewRequest("GET", "http://example.org", nil) + + authenticatedUser, isAuthenticated, err := authRequestHandler.AuthenticateRequest(req) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if isAuthenticated { + t.Errorf("Unexpectedly authenticated: %v", isAuthenticated) + } + if authenticatedUser != nil { + t.Errorf("Unexpected authenticatedUser: %v", authenticatedUser) + } +} + +func TestAuthenticateRequestNonePass(t *testing.T) { + handler1 := &mockAuthRequestHandler{} + handler2 := &mockAuthRequestHandler{} + authRequestHandler := New(handler1, handler2) + req, _ := http.NewRequest("GET", "http://example.org", nil) + + _, isAuthenticated, err := authRequestHandler.AuthenticateRequest(req) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if isAuthenticated { + t.Errorf("Unexpectedly authenticated: %v", isAuthenticated) + } +} + +func TestAuthenticateRequestAdditiveErrors(t *testing.T) { + handler1 := &mockAuthRequestHandler{err: errors.New("first")} + handler2 := &mockAuthRequestHandler{err: errors.New("second")} + authRequestHandler := New(handler1, handler2) + req, _ := http.NewRequest("GET", "http://example.org", nil) + + _, isAuthenticated, err := authRequestHandler.AuthenticateRequest(req) + if err == nil { + t.Errorf("Expected an error") + } + if !strings.Contains(err.Error(), "first") { + t.Errorf("Expected error containing %v, got %v", "first", err) + } + if !strings.Contains(err.Error(), "second") { + t.Errorf("Expected error containing %v, got %v", "second", err) + } + if isAuthenticated { + t.Errorf("Unexpectedly authenticated: %v", isAuthenticated) + } +}