package integration

import (
	"testing"

	kapi "k8s.io/kubernetes/pkg/api"
	kapierrors "k8s.io/kubernetes/pkg/api/errors"
	kclient "k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/serviceaccount"

	authenticationapi "github.com/openshift/origin/pkg/auth/api"
	"github.com/openshift/origin/pkg/authorization/authorizer/scope"
	buildapi "github.com/openshift/origin/pkg/build/api"
	"github.com/openshift/origin/pkg/client"
	"github.com/openshift/origin/pkg/cmd/server/origin"
	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
	oauthapi "github.com/openshift/origin/pkg/oauth/api"
	userapi "github.com/openshift/origin/pkg/user/api"
	testutil "github.com/openshift/origin/test/util"
	testserver "github.com/openshift/origin/test/util/server"
)

func TestScopedTokens(t *testing.T) {
	testutil.RequireEtcd(t)
	defer testutil.DumpEtcdOnFailure(t)
	_, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	projectName := "hammer-project"
	userName := "harold"
	haroldClient, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, projectName, userName)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if _, err := haroldClient.Builds(projectName).List(kapi.ListOptions{}); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	haroldUser, err := haroldClient.Users().Get("~")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	whoamiOnlyToken := &oauthapi.OAuthAccessToken{
		ObjectMeta: kapi.ObjectMeta{Name: "whoami-token-plus-some-padding-here-to-make-the-limit"},
		ClientName: origin.OpenShiftCLIClientID,
		ExpiresIn:  200,
		Scopes:     []string{scope.UserInfo},
		UserName:   userName,
		UserUID:    string(haroldUser.UID),
	}
	if _, err := clusterAdminClient.OAuthAccessTokens().Create(whoamiOnlyToken); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	whoamiConfig := clientcmd.AnonymousClientConfig(clusterAdminClientConfig)
	whoamiConfig.BearerToken = whoamiOnlyToken.Name
	whoamiClient, err := client.New(&whoamiConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if _, err := whoamiClient.Builds(projectName).List(kapi.ListOptions{}); !kapierrors.IsForbidden(err) {
		t.Fatalf("unexpected error: %v", err)
	}

	user, err := whoamiClient.Users().Get("~")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if user.Name != userName {
		t.Fatalf("expected %v, got %v", userName, user.Name)
	}

	// try to impersonate a service account using this token
	whoamiConfig.Impersonate = serviceaccount.MakeUsername(projectName, "default")
	impersonatingClient, err := client.New(&whoamiConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	impersonatedUser, err := impersonatingClient.Users().Get("~")
	if !kapierrors.IsForbidden(err) {
		t.Fatalf("missing error: %v got user %#v", err, impersonatedUser)
	}
}

func TestScopedImpersonation(t *testing.T) {
	testutil.RequireEtcd(t)
	defer testutil.DumpEtcdOnFailure(t)
	_, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	projectName := "hammer-project"
	userName := "harold"
	if _, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, projectName, userName); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	err = clusterAdminClient.Get().
		SetHeader(authenticationapi.ImpersonateUserHeader, "harold").
		SetHeader(authenticationapi.ImpersonateUserScopeHeader, "user:info").
		Namespace(projectName).Resource("builds").Name("name").Do().Into(&buildapi.Build{})
	if !kapierrors.IsForbidden(err) {
		t.Fatalf("unexpected error: %v", err)
	}

	user := &userapi.User{}
	err = clusterAdminClient.Get().
		SetHeader(authenticationapi.ImpersonateUserHeader, "harold").
		SetHeader(authenticationapi.ImpersonateUserScopeHeader, "user:info").
		Resource("users").Name("~").Do().Into(user)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if user.Name != "harold" {
		t.Fatalf("expected %v, got %v", "harold", user.Name)
	}
}

func TestScopeEscalations(t *testing.T) {
	testutil.RequireEtcd(t)
	defer testutil.DumpEtcdOnFailure(t)
	_, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	projectName := "hammer-project"
	userName := "harold"
	haroldClient, err := testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, projectName, userName)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if _, err := haroldClient.Builds(projectName).List(kapi.ListOptions{}); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	haroldUser, err := haroldClient.Users().Get("~")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	nonEscalatingEditToken := &oauthapi.OAuthAccessToken{
		ObjectMeta: kapi.ObjectMeta{Name: "non-escalating-edit-plus-some-padding-here-to-make-the-limit"},
		ClientName: origin.OpenShiftCLIClientID,
		ExpiresIn:  200,
		Scopes:     []string{scope.ClusterRoleIndicator + "edit:*"},
		UserName:   userName,
		UserUID:    string(haroldUser.UID),
	}
	if _, err := clusterAdminClient.OAuthAccessTokens().Create(nonEscalatingEditToken); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	nonEscalatingEditConfig := clientcmd.AnonymousClientConfig(clusterAdminClientConfig)
	nonEscalatingEditConfig.BearerToken = nonEscalatingEditToken.Name
	nonEscalatingEditClient, err := kclient.New(&nonEscalatingEditConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if _, err := nonEscalatingEditClient.Secrets(projectName).List(kapi.ListOptions{}); !kapierrors.IsForbidden(err) {
		t.Fatalf("unexpected error: %v", err)
	}

	escalatingEditToken := &oauthapi.OAuthAccessToken{
		ObjectMeta: kapi.ObjectMeta{Name: "escalating-edit-plus-some-padding-here-to-make-the-limit"},
		ClientName: origin.OpenShiftCLIClientID,
		ExpiresIn:  200,
		Scopes:     []string{scope.ClusterRoleIndicator + "edit:*:!"},
		UserName:   userName,
		UserUID:    string(haroldUser.UID),
	}
	if _, err := clusterAdminClient.OAuthAccessTokens().Create(escalatingEditToken); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	escalatingEditConfig := clientcmd.AnonymousClientConfig(clusterAdminClientConfig)
	escalatingEditConfig.BearerToken = escalatingEditToken.Name
	escalatingEditClient, err := kclient.New(&escalatingEditConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if _, err := escalatingEditClient.Secrets(projectName).List(kapi.ListOptions{}); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestTokensWithIllegalScopes(t *testing.T) {
	testutil.RequireEtcd(t)
	defer testutil.DumpEtcdOnFailure(t)
	_, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	client := &oauthapi.OAuthClient{
		ObjectMeta: kapi.ObjectMeta{Name: "testing-client"},
		ScopeRestrictions: []oauthapi.ScopeRestriction{
			{ExactValues: []string{"user:info"}},
			{
				ClusterRole: &oauthapi.ClusterRoleScopeRestriction{
					RoleNames:       []string{"one", "two"},
					Namespaces:      []string{"alfa", "bravo"},
					AllowEscalation: false,
				},
			},
		},
	}
	if _, err := clusterAdminClient.OAuthClients().Create(client); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	clientAuthorizationTests := []struct {
		name string
		obj  *oauthapi.OAuthClientAuthorization
		fail bool
	}{
		{
			name: "no scopes",
			fail: true,
			obj: &oauthapi.OAuthClientAuthorization{
				ObjectMeta: kapi.ObjectMeta{Name: "testing-client"},
				ClientName: client.Name,
				UserName:   "name",
				UserUID:    "uid",
			},
		},
		{
			name: "denied literal",
			fail: true,
			obj: &oauthapi.OAuthClientAuthorization{
				ObjectMeta: kapi.ObjectMeta{Name: "testing-client"},
				ClientName: client.Name,
				UserName:   "name",
				UserUID:    "uid",
				Scopes:     []string{"user:info", "user:check-access"},
			},
		},
		{
			name: "denied role",
			fail: true,
			obj: &oauthapi.OAuthClientAuthorization{
				ObjectMeta: kapi.ObjectMeta{Name: "testing-client"},
				ClientName: client.Name,
				UserName:   "name",
				UserUID:    "uid",
				Scopes:     []string{"role:one:*"},
			},
		},
		{
			name: "ok role",
			obj: &oauthapi.OAuthClientAuthorization{
				ObjectMeta: kapi.ObjectMeta{Name: "testing-client"},
				ClientName: client.Name,
				UserName:   "name",
				UserUID:    "uid",
				Scopes:     []string{"role:one:bravo"},
			},
		},
	}
	for _, tc := range clientAuthorizationTests {
		_, err := clusterAdminClient.OAuthClientAuthorizations().Create(tc.obj)
		switch {
		case err == nil && !tc.fail:
		case err != nil && tc.fail:
		default:
			t.Errorf("%s: expected %v, got %v", tc.name, tc.fail, err)

		}
	}

	accessTokenTests := []struct {
		name string
		obj  *oauthapi.OAuthAccessToken
		fail bool
	}{
		{
			name: "no scopes",
			fail: true,
			obj: &oauthapi.OAuthAccessToken{
				ObjectMeta: kapi.ObjectMeta{Name: "tokenlongenoughtobecreatedwithoutfailing"},
				ClientName: client.Name,
				UserName:   "name",
				UserUID:    "uid",
			},
		},
		{
			name: "denied literal",
			fail: true,
			obj: &oauthapi.OAuthAccessToken{
				ObjectMeta: kapi.ObjectMeta{Name: "tokenlongenoughtobecreatedwithoutfailing"},
				ClientName: client.Name,
				UserName:   "name",
				UserUID:    "uid",
				Scopes:     []string{"user:info", "user:check-access"},
			},
		},
		{
			name: "denied role",
			fail: true,
			obj: &oauthapi.OAuthAccessToken{
				ObjectMeta: kapi.ObjectMeta{Name: "tokenlongenoughtobecreatedwithoutfailing"},
				ClientName: client.Name,
				UserName:   "name",
				UserUID:    "uid",
				Scopes:     []string{"role:one:*"},
			},
		},
		{
			name: "ok role",
			obj: &oauthapi.OAuthAccessToken{
				ObjectMeta: kapi.ObjectMeta{Name: "tokenlongenoughtobecreatedwithoutfailing"},
				ClientName: client.Name,
				UserName:   "name",
				UserUID:    "uid",
				Scopes:     []string{"role:one:bravo"},
			},
		},
	}
	for _, tc := range accessTokenTests {
		_, err := clusterAdminClient.OAuthAccessTokens().Create(tc.obj)
		switch {
		case err == nil && !tc.fail:
		case err != nil && tc.fail:
		default:
			t.Errorf("%s: expected %v, got %v", tc.name, tc.fail, err)

		}
	}

	authorizeTokenTests := []struct {
		name string
		obj  *oauthapi.OAuthAuthorizeToken
		fail bool
	}{
		{
			name: "no scopes",
			fail: true,
			obj: &oauthapi.OAuthAuthorizeToken{
				ObjectMeta: kapi.ObjectMeta{Name: "tokenlongenoughtobecreatedwithoutfailing"},
				ClientName: client.Name,
				UserName:   "name",
				UserUID:    "uid",
			},
		},
		{
			name: "denied literal",
			fail: true,
			obj: &oauthapi.OAuthAuthorizeToken{
				ObjectMeta: kapi.ObjectMeta{Name: "tokenlongenoughtobecreatedwithoutfailing"},
				ClientName: client.Name,
				UserName:   "name",
				UserUID:    "uid",
				Scopes:     []string{"user:info", "user:check-access"},
			},
		},
		{
			name: "denied role",
			fail: true,
			obj: &oauthapi.OAuthAuthorizeToken{
				ObjectMeta: kapi.ObjectMeta{Name: "tokenlongenoughtobecreatedwithoutfailing"},
				ClientName: client.Name,
				UserName:   "name",
				UserUID:    "uid",
				Scopes:     []string{"role:one:*"},
			},
		},
		{
			name: "ok role",
			obj: &oauthapi.OAuthAuthorizeToken{
				ObjectMeta: kapi.ObjectMeta{Name: "tokenlongenoughtobecreatedwithoutfailing"},
				ClientName: client.Name,
				UserName:   "name",
				UserUID:    "uid",
				Scopes:     []string{"role:one:bravo"},
			},
		},
	}
	for _, tc := range authorizeTokenTests {
		_, err := clusterAdminClient.OAuthAuthorizeTokens().Create(tc.obj)
		switch {
		case err == nil && !tc.fail:
		case err != nil && tc.fail:
		default:
			t.Errorf("%s: expected %v, got %v", tc.name, tc.fail, err)

		}
	}

}