package validation

import (
	"testing"

	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/util/validation/field"

	oapi "github.com/openshift/origin/pkg/oauth/api"
)

func TestValidateRedirectURI(t *testing.T) {
	allowed := []string{
		// Empty allowed
		"",

		// Non-absolute
		"server",

		// No protocol
		"//server",

		// Case insensitive
		"HTTP://server",
		"HTTPS://server",

		// Normal paths
		"https://server",
		"https://server",

		// With ports
		"https://server:",
		"https://server:port",

		// With or without paths, with or without trailing slashes
		"https://server:port/",
		"https://server:port/path-segment",
		"https://server:port/path-segment/",

		// Things that are close to disallowed path segments
		"https://server:port/...",
		"https://server:port/.../",
		"https://server:port/path-segment/...",
		"https://server:port/path-segment/path.",
		"https://server:port/path-segment/path./",

		// Double slashes
		"https://server:port/path-segment//path",

		// Queries
		"http://server/path?",
		"http://server/path?query",
		"http://server/path?query=value",

		// Empty fragments
		"http://server/path?query=value#",
	}
	for i, u := range allowed {
		ok, msg := ValidateRedirectURI(u)
		if !ok {
			t.Errorf("%d expected %q to be allowed, but got error message %q", i, u, msg)
		}
	}

	disallowed := []string{
		// invalid URL
		"://server:port/",

		// . or .. segments
		"http://server/.",
		"http://server/./",
		"http://server/..",
		"http://server/../",
		"http://server/path/..",
		"http://server/path/../",
		"http://server/path/../path",

		// Fragments
		"http://server/path?query#test",
	}
	for i, u := range disallowed {
		ok, _ := ValidateRedirectURI(u)
		if ok {
			t.Errorf("%d expected %q to be disallowed", i, u)
		}
	}
}

func TestValidateClientAuthorization(t *testing.T) {
	errs := ValidateClientAuthorization(&oapi.OAuthClientAuthorization{
		ObjectMeta: api.ObjectMeta{Name: "myusername:myclientname"},
		ClientName: "myclientname",
		UserName:   "myusername",
		UserUID:    "myuseruid",
	})
	if len(errs) != 0 {
		t.Errorf("expected success: %v", errs)
	}

	errorCases := map[string]struct {
		A oapi.OAuthClientAuthorization
		T field.ErrorType
		F string
	}{
		"zero-length name": {
			A: oapi.OAuthClientAuthorization{
				ClientName: "myclientname",
				UserName:   "myusername",
				UserUID:    "myuseruid",
			},
			T: field.ErrorTypeRequired,
			F: "metadata.name",
		},
		"invalid name": {
			A: oapi.OAuthClientAuthorization{
				ObjectMeta: api.ObjectMeta{Name: "anotheruser:anotherclient"},
				ClientName: "myclientname",
				UserName:   "myusername",
				UserUID:    "myuseruid",
			},
			T: field.ErrorTypeInvalid,
			F: "metadata.name",
		},
		"disallowed namespace": {
			A: oapi.OAuthClientAuthorization{
				ObjectMeta: api.ObjectMeta{Name: "myusername:myclientname", Namespace: "foo"},
				ClientName: "myclientname",
				UserName:   "myusername",
				UserUID:    "myuseruid",
			},
			T: field.ErrorTypeForbidden,
			F: "metadata.namespace",
		},
		"no scope handler": {
			A: oapi.OAuthClientAuthorization{
				ObjectMeta: api.ObjectMeta{Name: "myusername:myclientname"},
				ClientName: "myclientname",
				UserName:   "myusername",
				UserUID:    "myuseruid",
				Scopes:     []string{"invalid"},
			},
			T: field.ErrorTypeInvalid,
			F: "scopes[0]",
		},
		"bad scope": {
			A: oapi.OAuthClientAuthorization{
				ObjectMeta: api.ObjectMeta{Name: "myusername:myclientname"},
				ClientName: "myclientname",
				UserName:   "myusername",
				UserUID:    "myuseruid",
				Scopes:     []string{"user:dne"},
			},
			T: field.ErrorTypeInvalid,
			F: "scopes[0]",
		},
	}
	for k, v := range errorCases {
		errs := ValidateClientAuthorization(&v.A)
		if len(errs) == 0 {
			t.Errorf("expected failure %s for %v", k, v.A)
			continue
		}
		for i := range errs {
			if errs[i].Type != v.T {
				t.Errorf("%s: expected errors to have type %s  GOT: %v", k, v.T, errs[i])
			}
			if errs[i].Field != v.F {
				t.Errorf("%s: expected errors to have field %s  GOT: %v", k, v.F, errs[i])
			}
		}
	}
}

func TestValidateClient(t *testing.T) {
	errs := ValidateClient(&oapi.OAuthClient{
		ObjectMeta: api.ObjectMeta{Name: "client-name"},
	})
	if len(errs) != 0 {
		t.Errorf("expected success: %v", errs)
	}

	errorCases := map[string]struct {
		Client oapi.OAuthClient
		T      field.ErrorType
		F      string
	}{
		"zero-length name": {
			Client: oapi.OAuthClient{},
			T:      field.ErrorTypeRequired,
			F:      "metadata.name",
		},
		"disallowed namespace": {
			Client: oapi.OAuthClient{ObjectMeta: api.ObjectMeta{Name: "name", Namespace: "foo"}},
			T:      field.ErrorTypeForbidden,
			F:      "metadata.namespace",
		},
		"literal must have value": {
			Client: oapi.OAuthClient{
				ObjectMeta:        api.ObjectMeta{Name: "client-name"},
				ScopeRestrictions: []oapi.ScopeRestriction{{ExactValues: []string{""}}},
			},
			T: field.ErrorTypeInvalid,
			F: "scopeRestrictions[0].literals[0]",
		},
		"must have role names": {
			Client: oapi.OAuthClient{
				ObjectMeta: api.ObjectMeta{Name: "client-name"},
				ScopeRestrictions: []oapi.ScopeRestriction{
					{
						ClusterRole: &oapi.ClusterRoleScopeRestriction{Namespaces: []string{"b"}},
					},
				},
			},
			T: field.ErrorTypeRequired,
			F: "scopeRestrictions[0].clusterRole.roleNames",
		},
		"must have namespaces": {
			Client: oapi.OAuthClient{
				ObjectMeta: api.ObjectMeta{Name: "client-name"},
				ScopeRestrictions: []oapi.ScopeRestriction{
					{
						ClusterRole: &oapi.ClusterRoleScopeRestriction{RoleNames: []string{"a"}},
					},
				},
			},
			T: field.ErrorTypeRequired,
			F: "scopeRestrictions[0].clusterRole.namespaces",
		},
	}
	for k, v := range errorCases {
		errs := ValidateClient(&v.Client)
		if len(errs) == 0 {
			t.Errorf("expected failure %s for %v", k, v.Client)
			continue
		}
		for i := range errs {
			if errs[i].Type != v.T {
				t.Errorf("%s: expected errors to have type %s: %v", k, v.T, errs[i])
			}
			if errs[i].Field != v.F {
				t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i])
			}
		}
	}
}

func TestValidateAccessTokens(t *testing.T) {
	errs := ValidateAccessToken(&oapi.OAuthAccessToken{
		ObjectMeta: api.ObjectMeta{Name: "accessTokenNameWithMinimumLength"},
		ClientName: "myclient",
		UserName:   "myusername",
		UserUID:    "myuseruid",
	})
	if len(errs) != 0 {
		t.Errorf("expected success: %v", errs)
	}

	errorCases := map[string]struct {
		Token oapi.OAuthAccessToken
		T     field.ErrorType
		F     string
	}{
		"zero-length name": {
			Token: oapi.OAuthAccessToken{
				ClientName: "myclient",
				UserName:   "myusername",
				UserUID:    "myuseruid",
			},
			T: field.ErrorTypeRequired,
			F: "metadata.name",
		},
		"disallowed namespace": {
			Token: oapi.OAuthAccessToken{
				ObjectMeta: api.ObjectMeta{Name: "accessTokenNameWithMinimumLength", Namespace: "foo"},
				ClientName: "myclient",
				UserName:   "myusername",
				UserUID:    "myuseruid",
			},
			T: field.ErrorTypeForbidden,
			F: "metadata.namespace",
		},
		"no scope handler": {
			Token: oapi.OAuthAccessToken{
				ObjectMeta: api.ObjectMeta{Name: "accessTokenNameWithMinimumLength"},
				ClientName: "myclient",
				UserName:   "myusername",
				UserUID:    "myuseruid",
				Scopes:     []string{"invalid"},
			},
			T: field.ErrorTypeInvalid,
			F: "scopes[0]",
		},
		"bad scope": {
			Token: oapi.OAuthAccessToken{
				ObjectMeta: api.ObjectMeta{Name: "accessTokenNameWithMinimumLength"},
				ClientName: "myclient",
				UserName:   "myusername",
				UserUID:    "myuseruid",
				Scopes:     []string{"user:dne"},
			},
			T: field.ErrorTypeInvalid,
			F: "scopes[0]",
		},
	}
	for k, v := range errorCases {
		errs := ValidateAccessToken(&v.Token)
		if len(errs) == 0 {
			t.Errorf("expected failure %s for %v", k, v.Token)
			continue
		}
		for i := range errs {
			if errs[i].Type != v.T {
				t.Errorf("%s: expected errors to have type %s: %v", k, v.T, errs[i])
			}
			if errs[i].Field != v.F {
				t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i])
			}
		}
	}
}

func TestValidateAuthorizeTokens(t *testing.T) {
	errs := ValidateAuthorizeToken(&oapi.OAuthAuthorizeToken{
		ObjectMeta: api.ObjectMeta{Name: "authorizeTokenNameWithMinimumLength"},
		ClientName: "myclient",
		UserName:   "myusername",
		UserUID:    "myuseruid",
		Scopes:     []string{`user:info`},
	})
	if len(errs) != 0 {
		t.Errorf("expected success: %v", errs)
	}

	errorCases := map[string]struct {
		Token oapi.OAuthAuthorizeToken
		T     field.ErrorType
		F     string
	}{
		"zero-length name": {
			Token: oapi.OAuthAuthorizeToken{
				ClientName: "myclient",
				UserName:   "myusername",
				UserUID:    "myuseruid",
			},
			T: field.ErrorTypeRequired,
			F: "metadata.name",
		},
		"zero-length client name": {
			Token: oapi.OAuthAuthorizeToken{
				ObjectMeta: api.ObjectMeta{Name: "authorizeTokenNameWithMinimumLength"},
				UserName:   "myusername",
				UserUID:    "myuseruid",
			},
			T: field.ErrorTypeRequired,
			F: "clientName",
		},
		"zero-length user name": {
			Token: oapi.OAuthAuthorizeToken{
				ObjectMeta: api.ObjectMeta{Name: "authorizeTokenNameWithMinimumLength"},
				ClientName: "myclient",
				UserUID:    "myuseruid",
			},
			T: field.ErrorTypeRequired,
			F: "userName",
		},
		"zero-length user uid": {
			Token: oapi.OAuthAuthorizeToken{
				ObjectMeta: api.ObjectMeta{Name: "authorizeTokenNameWithMinimumLength"},
				ClientName: "myclient",
				UserName:   "myusername",
			},
			T: field.ErrorTypeRequired,
			F: "userUID",
		},
		"disallowed namespace": {
			Token: oapi.OAuthAuthorizeToken{
				ObjectMeta: api.ObjectMeta{Name: "authorizeTokenNameWithMinimumLength", Namespace: "foo"},
				ClientName: "myclient",
				UserName:   "myusername",
				UserUID:    "myuseruid",
			},
			T: field.ErrorTypeForbidden,
			F: "metadata.namespace",
		},
		"no scope handler": {
			Token: oapi.OAuthAuthorizeToken{
				ObjectMeta: api.ObjectMeta{Name: "authorizeTokenNameWithMinimumLength"},
				ClientName: "myclient",
				UserName:   "myusername",
				UserUID:    "myuseruid",
				Scopes:     []string{"invalid"},
			},
			T: field.ErrorTypeInvalid,
			F: "scopes[0]",
		},
		"bad scope": {
			Token: oapi.OAuthAuthorizeToken{
				ObjectMeta: api.ObjectMeta{Name: "authorizeTokenNameWithMinimumLength"},
				ClientName: "myclient",
				UserName:   "myusername",
				UserUID:    "myuseruid",
				Scopes:     []string{"user:dne"},
			},
			T: field.ErrorTypeInvalid,
			F: "scopes[0]",
		},
		"illegal character": {
			Token: oapi.OAuthAuthorizeToken{
				ObjectMeta: api.ObjectMeta{Name: "authorizeTokenNameWithMinimumLength"},
				ClientName: "myclient",
				UserName:   "myusername",
				UserUID:    "myuseruid",
				Scopes:     []string{`role:asdf":foo`},
			},
			T: field.ErrorTypeInvalid,
			F: "scopes[0]",
		},
	}
	for k, v := range errorCases {
		errs := ValidateAuthorizeToken(&v.Token)
		if len(errs) == 0 {
			t.Errorf("expected failure %s for %v", k, v.Token)
			continue
		}
		for i := range errs {
			if errs[i].Type != v.T {
				t.Errorf("%s: expected errors to have type %s: %v", k, v.T, errs[i])
			}
			if errs[i].Field != v.F {
				t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i])
			}
		}
	}
}

func TestValidateAccessTokensUpdate(t *testing.T) {
	valid := &oapi.OAuthAccessToken{
		ObjectMeta: api.ObjectMeta{Name: "accessTokenNameWithMinimumLength", ResourceVersion: "1"},
		ClientName: "myclient",
		UserName:   "myusername",
		UserUID:    "myuseruid",
	}
	errs := ValidateAccessTokenUpdate(valid, valid)
	if len(errs) != 0 {
		t.Errorf("expected success: %v", errs)
	}

	errorCases := map[string]struct {
		Token  oapi.OAuthAccessToken
		Change func(*oapi.OAuthAccessToken)
		T      field.ErrorType
		F      string
	}{
		"change name": {
			Token: *valid,
			Change: func(obj *oapi.OAuthAccessToken) {
				obj.Name = ""
			},
			T: field.ErrorTypeInvalid,
			F: "metadata.name",
		},
		"change userName": {
			Token: *valid,
			Change: func(obj *oapi.OAuthAccessToken) {
				obj.UserName = ""
			},
			T: field.ErrorTypeInvalid,
			F: "[]",
		},
	}
	for k, v := range errorCases {
		copied, _ := api.Scheme.Copy(&v.Token)
		newToken := copied.(*oapi.OAuthAccessToken)
		v.Change(newToken)
		errs := ValidateAccessTokenUpdate(newToken, &v.Token)
		if len(errs) == 0 {
			t.Errorf("expected failure %s for %v", k, v.Token)
			continue
		}
		for i := range errs {
			if errs[i].Type != v.T {
				t.Errorf("%s: expected errors to have type %s: %v", k, v.T, errs[i])
			}
			if errs[i].Field != v.F {
				t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i])
			}
		}
	}
}

func TestValidateAuthorizeTokensUpdate(t *testing.T) {
	valid := &oapi.OAuthAuthorizeToken{
		ObjectMeta: api.ObjectMeta{Name: "authorizeTokenNameWithMinimumLength", ResourceVersion: "1"},
		ClientName: "myclient",
		UserName:   "myusername",
		UserUID:    "myuseruid",
		Scopes:     []string{`user:info`},
	}
	errs := ValidateAuthorizeTokenUpdate(valid, valid)
	if len(errs) != 0 {
		t.Errorf("expected success: %v", errs)
	}

	errorCases := map[string]struct {
		Token  oapi.OAuthAuthorizeToken
		Change func(*oapi.OAuthAuthorizeToken)
		T      field.ErrorType
		F      string
	}{
		"change name": {
			Token: *valid,
			Change: func(obj *oapi.OAuthAuthorizeToken) {
				obj.Name = ""
			},
			T: field.ErrorTypeInvalid,
			F: "metadata.name",
		},
		"change userUID": {
			Token: *valid,
			Change: func(obj *oapi.OAuthAuthorizeToken) {
				obj.UserUID = ""
			},
			T: field.ErrorTypeInvalid,
			F: "[]",
		},
	}
	for k, v := range errorCases {
		copied, _ := api.Scheme.Copy(&v.Token)
		newToken := copied.(*oapi.OAuthAuthorizeToken)
		v.Change(newToken)
		errs := ValidateAuthorizeTokenUpdate(newToken, &v.Token)
		if len(errs) == 0 {
			t.Errorf("expected failure %s for %v", k, v.Token)
			continue
		}
		for i := range errs {
			if errs[i].Type != v.T {
				t.Errorf("%s: expected errors to have type %s: %v", k, v.T, errs[i])
			}
			if errs[i].Field != v.F {
				t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i])
			}
		}
	}
}