// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package consul

import (
	"context"
	"encoding/base64"
	"fmt"
	"log"
	"os"
	"reflect"
	"strings"
	"testing"
	"time"

	consulapi "github.com/hashicorp/consul/api"
	"github.com/hashicorp/vault/helper/testhelpers/consul"
	logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical"
	"github.com/hashicorp/vault/sdk/logical"
	"github.com/mitchellh/mapstructure"
)

func TestBackend_Config_Access(t *testing.T) {
	t.Run("config_access", func(t *testing.T) {
		t.Parallel()
		t.Run("pre-1.4.0", func(t *testing.T) {
			t.Parallel()
			testBackendConfigAccess(t, "1.3.1", true)
		})
		t.Run("post-1.4.0", func(t *testing.T) {
			t.Parallel()
			testBackendConfigAccess(t, "", true)
		})
		t.Run("pre-1.4.0 automatic-bootstrap", func(t *testing.T) {
			t.Parallel()
			testBackendConfigAccess(t, "1.3.1", false)
		})
		t.Run("post-1.4.0 automatic-bootstrap", func(t *testing.T) {
			t.Parallel()
			testBackendConfigAccess(t, "", false)
		})
	})
}

func testBackendConfigAccess(t *testing.T, version string, autoBootstrap bool) {
	config := logical.TestBackendConfig()
	config.StorageView = &logical.InmemStorage{}
	b, err := Factory(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, autoBootstrap)
	defer cleanup()

	connData := map[string]interface{}{
		"address": consulConfig.Address(),
	}
	if autoBootstrap || strings.HasPrefix(version, "1.3") {
		connData["token"] = consulConfig.Token
	}

	confReq := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "config/access",
		Storage:   config.StorageView,
		Data:      connData,
	}

	resp, err := b.HandleRequest(context.Background(), confReq)
	if err != nil || (resp != nil && resp.IsError()) || resp != nil {
		t.Fatalf("failed to write configuration: resp:%#v err:%s", resp, err)
	}

	confReq.Operation = logical.ReadOperation
	resp, err = b.HandleRequest(context.Background(), confReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("failed to write configuration: resp:%#v err:%s", resp, err)
	}

	expected := map[string]interface{}{
		"address": connData["address"].(string),
		"scheme":  "http",
	}
	if !reflect.DeepEqual(expected, resp.Data) {
		t.Fatalf("bad: expected:%#v\nactual:%#v\n", expected, resp.Data)
	}
	if resp.Data["token"] != nil {
		t.Fatalf("token should not be set in the response")
	}
}

func TestBackend_Renew_Revoke(t *testing.T) {
	t.Run("renew_revoke", func(t *testing.T) {
		t.Parallel()
		t.Run("pre-1.4.0", func(t *testing.T) {
			t.Parallel()
			testBackendRenewRevoke(t, "1.3.1")
		})
		t.Run("post-1.4.0", func(t *testing.T) {
			t.Parallel()
			t.Run("legacy", func(t *testing.T) {
				t.Parallel()
				testBackendRenewRevoke(t, "1.4.4")
			})

			t.Run("param-policies", func(t *testing.T) {
				t.Parallel()
				testBackendRenewRevoke14(t, "", "policies")
			})
			t.Run("param-consul_policies", func(t *testing.T) {
				t.Parallel()
				testBackendRenewRevoke14(t, "", "consul_policies")
			})
			t.Run("both-params", func(t *testing.T) {
				t.Parallel()
				testBackendRenewRevoke14(t, "", "both")
			})
		})
	})
}

func testBackendRenewRevoke(t *testing.T, version string) {
	config := logical.TestBackendConfig()
	config.StorageView = &logical.InmemStorage{}
	b, err := Factory(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, true)
	defer cleanup()

	connData := map[string]interface{}{
		"address": consulConfig.Address(),
		"token":   consulConfig.Token,
	}

	req := &logical.Request{
		Storage:   config.StorageView,
		Operation: logical.UpdateOperation,
		Path:      "config/access",
		Data:      connData,
	}
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	req.Path = "roles/test"
	req.Data = map[string]interface{}{
		"policy": base64.StdEncoding.EncodeToString([]byte(testPolicy)),
		"lease":  "6h",
	}
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	req.Operation = logical.ReadOperation
	req.Path = "creds/test"
	resp, err := b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatal("resp nil")
	}
	if resp.IsError() {
		t.Fatalf("resp is error: %v", resp.Error())
	}

	generatedSecret := resp.Secret
	generatedSecret.TTL = 6 * time.Hour

	var d struct {
		Token string `mapstructure:"token"`
	}
	if err := mapstructure.Decode(resp.Data, &d); err != nil {
		t.Fatal(err)
	}

	// Build a client and verify that the credentials work
	consulapiConfig := consulapi.DefaultConfig()
	consulapiConfig.Address = connData["address"].(string)
	consulapiConfig.Token = d.Token
	client, err := consulapi.NewClient(consulapiConfig)
	if err != nil {
		t.Fatal(err)
	}

	_, err = client.KV().Put(&consulapi.KVPair{
		Key:   "foo",
		Value: []byte("bar"),
	}, nil)
	if err != nil {
		t.Fatal(err)
	}

	req.Operation = logical.RenewOperation
	req.Secret = generatedSecret
	resp, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatal("got nil response from renew")
	}

	req.Operation = logical.RevokeOperation
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	_, err = client.KV().Put(&consulapi.KVPair{
		Key:   "foo",
		Value: []byte("bar"),
	}, nil)
	if err == nil {
		t.Fatal("err: expected error")
	}
}

func testBackendRenewRevoke14(t *testing.T, version string, policiesParam string) {
	config := logical.TestBackendConfig()
	config.StorageView = &logical.InmemStorage{}
	b, err := Factory(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, true)
	defer cleanup()

	connData := map[string]interface{}{
		"address": consulConfig.Address(),
		"token":   consulConfig.Token,
	}

	req := &logical.Request{
		Storage:   config.StorageView,
		Operation: logical.UpdateOperation,
		Path:      "config/access",
		Data:      connData,
	}
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	req.Path = "roles/test"
	req.Data = map[string]interface{}{
		"lease": "6h",
	}
	if policiesParam == "both" {
		req.Data["policies"] = []string{"wrong-name"}
		req.Data["consul_policies"] = []string{"test"}
	} else {
		req.Data[policiesParam] = []string{"test"}
	}

	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	read := &logical.Request{
		Storage:   config.StorageView,
		Operation: logical.ReadOperation,
		Path:      "roles/test",
		Data:      connData,
	}
	roleResp, err := b.HandleRequest(context.Background(), read)

	expectExtract := roleResp.Data["consul_policies"]
	respExtract := roleResp.Data[policiesParam]
	if respExtract != nil {
		if expectExtract.([]string)[0] != respExtract.([]string)[0] {
			t.Errorf("mismatch: response consul_policies '%s' does not match '[test]'", roleResp.Data["consul_policies"])
		}
	}

	req.Operation = logical.ReadOperation
	req.Path = "creds/test"
	resp, err := b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatal("resp nil")
	}
	if resp.IsError() {
		t.Fatalf("resp is error: %v", resp.Error())
	}

	generatedSecret := resp.Secret
	generatedSecret.TTL = 6 * time.Hour

	var d struct {
		Token    string `mapstructure:"token"`
		Accessor string `mapstructure:"accessor"`
	}
	if err := mapstructure.Decode(resp.Data, &d); err != nil {
		t.Fatal(err)
	}

	// Build a client and verify that the credentials work
	consulapiConfig := consulapi.DefaultNonPooledConfig()
	consulapiConfig.Address = connData["address"].(string)
	consulapiConfig.Token = d.Token
	client, err := consulapi.NewClient(consulapiConfig)
	if err != nil {
		t.Fatal(err)
	}

	_, err = client.Catalog(), nil
	if err != nil {
		t.Fatal(err)
	}

	req.Operation = logical.RenewOperation
	req.Secret = generatedSecret
	resp, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatal("got nil response from renew")
	}

	req.Operation = logical.RevokeOperation
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	// Build a management client and verify that the token does not exist anymore
	consulmgmtConfig := consulapi.DefaultNonPooledConfig()
	consulmgmtConfig.Address = connData["address"].(string)
	consulmgmtConfig.Token = connData["token"].(string)
	mgmtclient, err := consulapi.NewClient(consulmgmtConfig)
	if err != nil {
		t.Fatal(err)
	}
	q := &consulapi.QueryOptions{
		Datacenter: "DC1",
	}

	_, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q)
	if err == nil {
		t.Fatal("err: expected error")
	}
}

func TestBackend_LocalToken(t *testing.T) {
	config := logical.TestBackendConfig()
	config.StorageView = &logical.InmemStorage{}
	b, err := Factory(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	cleanup, consulConfig := consul.PrepareTestContainer(t, "", false, true)
	defer cleanup()

	connData := map[string]interface{}{
		"address": consulConfig.Address(),
		"token":   consulConfig.Token,
	}

	req := &logical.Request{
		Storage:   config.StorageView,
		Operation: logical.UpdateOperation,
		Path:      "config/access",
		Data:      connData,
	}
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	req.Path = "roles/test"
	req.Data = map[string]interface{}{
		"consul_policies": []string{"test"},
		"ttl":             "6h",
		"local":           false,
	}
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	req.Path = "roles/test_local"
	req.Data = map[string]interface{}{
		"consul_policies": []string{"test"},
		"ttl":             "6h",
		"local":           true,
	}
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	req.Operation = logical.ReadOperation
	req.Path = "creds/test"
	resp, err := b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatal("resp nil")
	}
	if resp.IsError() {
		t.Fatalf("resp is error: %v", resp.Error())
	}

	var d struct {
		Token    string `mapstructure:"token"`
		Accessor string `mapstructure:"accessor"`
		Local    bool   `mapstructure:"local"`
	}
	if err := mapstructure.Decode(resp.Data, &d); err != nil {
		t.Fatal(err)
	}

	if d.Local {
		t.Fatalf("requested global token, got local one")
	}

	// Build a client and verify that the credentials work
	consulapiConfig := consulapi.DefaultNonPooledConfig()
	consulapiConfig.Address = connData["address"].(string)
	consulapiConfig.Token = d.Token
	client, err := consulapi.NewClient(consulapiConfig)
	if err != nil {
		t.Fatal(err)
	}

	_, err = client.Catalog(), nil
	if err != nil {
		t.Fatal(err)
	}

	req.Operation = logical.ReadOperation
	req.Path = "creds/test_local"
	resp, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatal("resp nil")
	}
	if resp.IsError() {
		t.Fatalf("resp is error: %v", resp.Error())
	}

	if err := mapstructure.Decode(resp.Data, &d); err != nil {
		t.Fatal(err)
	}

	if !d.Local {
		t.Fatalf("requested local token, got global one")
	}

	// Build a client and verify that the credentials work
	consulapiConfig = consulapi.DefaultNonPooledConfig()
	consulapiConfig.Address = connData["address"].(string)
	consulapiConfig.Token = d.Token
	client, err = consulapi.NewClient(consulapiConfig)
	if err != nil {
		t.Fatal(err)
	}

	_, err = client.Catalog(), nil
	if err != nil {
		t.Fatal(err)
	}
}

func TestBackend_Management(t *testing.T) {
	t.Run("management", func(t *testing.T) {
		t.Parallel()
		t.Run("pre-1.4.0", func(t *testing.T) {
			t.Parallel()
			testBackendManagement(t, "1.3.1")
		})
		t.Run("post-1.4.0", func(t *testing.T) {
			t.Parallel()
			testBackendManagement(t, "1.4.4")
		})

		testBackendManagement(t, "1.10.8")
	})
}

func testBackendManagement(t *testing.T, version string) {
	config := logical.TestBackendConfig()
	config.StorageView = &logical.InmemStorage{}
	b, err := Factory(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, true)
	defer cleanup()

	connData := map[string]interface{}{
		"address": consulConfig.Address(),
		"token":   consulConfig.Token,
	}

	logicaltest.Test(t, logicaltest.TestCase{
		LogicalBackend: b,
		Steps: []logicaltest.TestStep{
			testAccStepConfig(t, connData),
			testAccStepWriteManagementPolicy(t, "test", ""),
			testAccStepReadManagementToken(t, "test", connData),
		},
	})
}

func TestBackend_Basic(t *testing.T) {
	t.Run("basic", func(t *testing.T) {
		t.Parallel()
		t.Run("pre-1.4.0", func(t *testing.T) {
			t.Parallel()
			testBackendBasic(t, "1.3.1")
		})
		t.Run("post-1.4.0", func(t *testing.T) {
			t.Parallel()
			t.Run("legacy", func(t *testing.T) {
				t.Parallel()
				testBackendBasic(t, "1.4.4")
			})

			testBackendBasic(t, "1.10.8")
		})
	})
}

func testBackendBasic(t *testing.T, version string) {
	config := logical.TestBackendConfig()
	config.StorageView = &logical.InmemStorage{}
	b, err := Factory(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, true)
	defer cleanup()

	connData := map[string]interface{}{
		"address": consulConfig.Address(),
		"token":   consulConfig.Token,
	}

	logicaltest.Test(t, logicaltest.TestCase{
		LogicalBackend: b,
		Steps: []logicaltest.TestStep{
			testAccStepConfig(t, connData),
			testAccStepWritePolicy(t, "test", testPolicy, ""),
			testAccStepReadToken(t, "test", connData),
		},
	})
}

func TestBackend_crud(t *testing.T) {
	b, _ := Factory(context.Background(), logical.TestBackendConfig())
	logicaltest.Test(t, logicaltest.TestCase{
		LogicalBackend: b,
		Steps: []logicaltest.TestStep{
			testAccStepWritePolicy(t, "test", testPolicy, ""),
			testAccStepWritePolicy(t, "test2", testPolicy, ""),
			testAccStepWritePolicy(t, "test3", testPolicy, ""),
			testAccStepReadPolicy(t, "test", testPolicy, 0),
			testAccStepListPolicy(t, []string{"test", "test2", "test3"}),
			testAccStepDeletePolicy(t, "test"),
		},
	})
}

func TestBackend_role_lease(t *testing.T) {
	b, _ := Factory(context.Background(), logical.TestBackendConfig())
	logicaltest.Test(t, logicaltest.TestCase{
		LogicalBackend: b,
		Steps: []logicaltest.TestStep{
			testAccStepWritePolicy(t, "test", testPolicy, "6h"),
			testAccStepReadPolicy(t, "test", testPolicy, 6*time.Hour),
			testAccStepDeletePolicy(t, "test"),
		},
	})
}

func testAccStepConfig(t *testing.T, config map[string]interface{}) logicaltest.TestStep {
	return logicaltest.TestStep{
		Operation: logical.UpdateOperation,
		Path:      "config/access",
		Data:      config,
	}
}

func testAccStepReadToken(t *testing.T, name string, conf map[string]interface{}) logicaltest.TestStep {
	return logicaltest.TestStep{
		Operation: logical.ReadOperation,
		Path:      "creds/" + name,
		Check: func(resp *logical.Response) error {
			var d struct {
				Token string `mapstructure:"token"`
			}
			if err := mapstructure.Decode(resp.Data, &d); err != nil {
				return err
			}
			log.Printf("[WARN] Generated token: %s", d.Token)

			// Build a client and verify that the credentials work
			config := consulapi.DefaultConfig()
			config.Address = conf["address"].(string)
			config.Token = d.Token
			client, err := consulapi.NewClient(config)
			if err != nil {
				return err
			}

			log.Printf("[WARN] Verifying that the generated token works...")
			_, err = client.KV().Put(&consulapi.KVPair{
				Key:   "foo",
				Value: []byte("bar"),
			}, nil)
			if err != nil {
				return err
			}

			return nil
		},
	}
}

func testAccStepReadManagementToken(t *testing.T, name string, conf map[string]interface{}) logicaltest.TestStep {
	return logicaltest.TestStep{
		Operation: logical.ReadOperation,
		Path:      "creds/" + name,
		Check: func(resp *logical.Response) error {
			var d struct {
				Token string `mapstructure:"token"`
			}
			if err := mapstructure.Decode(resp.Data, &d); err != nil {
				return err
			}
			log.Printf("[WARN] Generated token: %s", d.Token)

			// Build a client and verify that the credentials work
			config := consulapi.DefaultConfig()
			config.Address = conf["address"].(string)
			config.Token = d.Token
			client, err := consulapi.NewClient(config)
			if err != nil {
				return err
			}

			log.Printf("[WARN] Verifying that the generated token works...")
			_, _, err = client.ACL().Create(&consulapi.ACLEntry{
				Type: "management",
				Name: "test2",
			}, nil)
			if err != nil {
				return err
			}

			return nil
		},
	}
}

func testAccStepWritePolicy(t *testing.T, name string, policy string, lease string) logicaltest.TestStep {
	return logicaltest.TestStep{
		Operation: logical.UpdateOperation,
		Path:      "roles/" + name,
		Data: map[string]interface{}{
			"policy": base64.StdEncoding.EncodeToString([]byte(policy)),
			"lease":  lease,
		},
	}
}

func testAccStepWriteManagementPolicy(t *testing.T, name string, lease string) logicaltest.TestStep {
	return logicaltest.TestStep{
		Operation: logical.UpdateOperation,
		Path:      "roles/" + name,
		Data: map[string]interface{}{
			"token_type": "management",
			"lease":      lease,
		},
	}
}

func testAccStepReadPolicy(t *testing.T, name string, policy string, lease time.Duration) logicaltest.TestStep {
	return logicaltest.TestStep{
		Operation: logical.ReadOperation,
		Path:      "roles/" + name,
		Check: func(resp *logical.Response) error {
			policyRaw := resp.Data["policy"].(string)
			out, err := base64.StdEncoding.DecodeString(policyRaw)
			if err != nil {
				return err
			}
			if string(out) != policy {
				return fmt.Errorf("mismatch: %s %s", out, policy)
			}

			l := resp.Data["lease"].(int64)
			if lease != time.Second*time.Duration(l) {
				return fmt.Errorf("mismatch: %v %v", l, lease)
			}
			return nil
		},
	}
}

func testAccStepListPolicy(t *testing.T, names []string) logicaltest.TestStep {
	return logicaltest.TestStep{
		Operation: logical.ListOperation,
		Path:      "roles/",
		Check: func(resp *logical.Response) error {
			respKeys := resp.Data["keys"].([]string)
			if !reflect.DeepEqual(respKeys, names) {
				return fmt.Errorf("mismatch: %#v %#v", respKeys, names)
			}
			return nil
		},
	}
}

func testAccStepDeletePolicy(t *testing.T, name string) logicaltest.TestStep {
	return logicaltest.TestStep{
		Operation: logical.DeleteOperation,
		Path:      "roles/" + name,
	}
}

func TestBackend_Roles(t *testing.T) {
	config := logical.TestBackendConfig()
	config.StorageView = &logical.InmemStorage{}
	b, err := Factory(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	cleanup, consulConfig := consul.PrepareTestContainer(t, "", false, true)
	defer cleanup()

	connData := map[string]interface{}{
		"address": consulConfig.Address(),
		"token":   consulConfig.Token,
	}

	req := &logical.Request{
		Storage:   config.StorageView,
		Operation: logical.UpdateOperation,
		Path:      "config/access",
		Data:      connData,
	}
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	// Create the consul_roles role
	req.Path = "roles/test-consul-roles"
	req.Data = map[string]interface{}{
		"consul_roles": []string{"role-test"},
		"lease":        "6h",
	}
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	req.Operation = logical.ReadOperation
	req.Path = "creds/test-consul-roles"
	resp, err := b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatal("resp nil")
	}
	if resp.IsError() {
		t.Fatalf("resp is error: %v", resp.Error())
	}

	generatedSecret := resp.Secret
	generatedSecret.TTL = 6 * time.Hour

	var d struct {
		Token    string `mapstructure:"token"`
		Accessor string `mapstructure:"accessor"`
	}
	if err := mapstructure.Decode(resp.Data, &d); err != nil {
		t.Fatal(err)
	}

	// Build a client and verify that the credentials work
	consulapiConfig := consulapi.DefaultNonPooledConfig()
	consulapiConfig.Address = connData["address"].(string)
	consulapiConfig.Token = d.Token
	client, err := consulapi.NewClient(consulapiConfig)
	if err != nil {
		t.Fatal(err)
	}

	_, err = client.Catalog(), nil
	if err != nil {
		t.Fatal(err)
	}

	req.Operation = logical.RenewOperation
	req.Secret = generatedSecret
	resp, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatal("got nil response from renew")
	}

	req.Operation = logical.RevokeOperation
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	// Build a management client and verify that the token does not exist anymore
	consulmgmtConfig := consulapi.DefaultNonPooledConfig()
	consulmgmtConfig.Address = connData["address"].(string)
	consulmgmtConfig.Token = connData["token"].(string)
	mgmtclient, err := consulapi.NewClient(consulmgmtConfig)
	if err != nil {
		t.Fatal(err)
	}
	q := &consulapi.QueryOptions{
		Datacenter: "DC1",
	}

	_, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q)
	if err == nil {
		t.Fatal("err: expected error")
	}
}

func TestBackend_Enterprise_Diff_Namespace_Revocation(t *testing.T) {
	if _, hasLicense := os.LookupEnv("CONSUL_LICENSE"); !hasLicense {
		t.Skip("Skipping: No enterprise license found")
	}

	testBackendEntDiffNamespaceRevocation(t)
}

func TestBackend_Enterprise_Diff_Partition_Revocation(t *testing.T) {
	if _, hasLicense := os.LookupEnv("CONSUL_LICENSE"); !hasLicense {
		t.Skip("Skipping: No enterprise license found")
	}

	testBackendEntDiffPartitionRevocation(t)
}

func TestBackend_Enterprise_Namespace(t *testing.T) {
	if _, hasLicense := os.LookupEnv("CONSUL_LICENSE"); !hasLicense {
		t.Skip("Skipping: No enterprise license found")
	}

	testBackendEntNamespace(t)
}

func TestBackend_Enterprise_Partition(t *testing.T) {
	if _, hasLicense := os.LookupEnv("CONSUL_LICENSE"); !hasLicense {
		t.Skip("Skipping: No enterprise license found")
	}

	testBackendEntPartition(t)
}

func testBackendEntDiffNamespaceRevocation(t *testing.T) {
	config := logical.TestBackendConfig()
	config.StorageView = &logical.InmemStorage{}
	b, err := Factory(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	cleanup, consulConfig := consul.PrepareTestContainer(t, "", true, true)
	defer cleanup()

	// Perform additional Consul configuration
	consulapiConfig := consulapi.DefaultNonPooledConfig()
	consulapiConfig.Address = consulConfig.Address()
	consulapiConfig.Token = consulConfig.Token
	client, err := consulapi.NewClient(consulapiConfig)
	if err != nil {
		t.Fatal(err)
	}

	// Create Policy in default namespace to manage ACLs in a different
	// namespace
	nsPol := &consulapi.ACLPolicy{
		Name:        "diff-ns-test",
		Description: "policy to test management of ACLs in one ns from another",
		Rules: `namespace "ns1" {
			acl="write"
		}
		`,
	}
	pol, _, err := client.ACL().PolicyCreate(nsPol, nil)
	if err != nil {
		t.Fatal(err)
	}

	// Create new Token in default namespace with new ACL
	cToken, _, err := client.ACL().TokenCreate(
		&consulapi.ACLToken{
			Policies: []*consulapi.ACLLink{{ID: pol.ID}},
		}, nil)
	if err != nil {
		t.Fatal(err)
	}

	// Write backend config
	connData := map[string]interface{}{
		"address": consulConfig.Address(),
		"token":   cToken.SecretID,
	}

	req := &logical.Request{
		Storage:   config.StorageView,
		Operation: logical.UpdateOperation,
		Path:      "config/access",
		Data:      connData,
	}
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	// Create the role in namespace "ns1"
	req.Path = "roles/test-ns"
	req.Data = map[string]interface{}{
		"consul_policies":  []string{"ns-test"},
		"lease":            "6h",
		"consul_namespace": "ns1",
	}
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	// Get Token
	req.Operation = logical.ReadOperation
	req.Path = "creds/test-ns"
	resp, err := b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatal("resp nil")
	}
	if resp.IsError() {
		t.Fatalf("resp is error: %v", resp.Error())
	}

	generatedSecret := resp.Secret
	generatedSecret.TTL = 6 * time.Hour

	// Verify Secret
	var d struct {
		Token           string `mapstructure:"token"`
		Accessor        string `mapstructure:"accessor"`
		ConsulNamespace string `mapstructure:"consul_namespace"`
	}
	if err := mapstructure.Decode(resp.Data, &d); err != nil {
		t.Fatal(err)
	}

	if d.ConsulNamespace != "ns1" {
		t.Fatalf("Failed to access namespace")
	}

	// Revoke the credential
	req.Operation = logical.RevokeOperation
	req.Secret = generatedSecret
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatalf("Revocation failed: %v", err)
	}

	// Build a management client and verify that the token does not exist anymore
	consulmgmtConfig := consulapi.DefaultNonPooledConfig()
	consulmgmtConfig.Address = connData["address"].(string)
	consulmgmtConfig.Token = connData["token"].(string)
	mgmtclient, err := consulapi.NewClient(consulmgmtConfig)
	if err != nil {
		t.Fatal(err)
	}
	q := &consulapi.QueryOptions{
		Datacenter: "DC1",
		Namespace:  "ns1",
	}

	_, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q)
	if err == nil {
		t.Fatal("err: expected error")
	}
}

func testBackendEntDiffPartitionRevocation(t *testing.T) {
	config := logical.TestBackendConfig()
	config.StorageView = &logical.InmemStorage{}
	b, err := Factory(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	cleanup, consulConfig := consul.PrepareTestContainer(t, "", true, true)
	defer cleanup()

	// Perform additional Consul configuration
	consulapiConfig := consulapi.DefaultNonPooledConfig()
	consulapiConfig.Address = consulConfig.Address()
	consulapiConfig.Token = consulConfig.Token
	client, err := consulapi.NewClient(consulapiConfig)
	if err != nil {
		t.Fatal(err)
	}

	// Create Policy in default partition to manage ACLs in a different
	// partition
	partPol := &consulapi.ACLPolicy{
		Name:        "diff-part-test",
		Description: "policy to test management of ACLs in one part from another",
		Rules: `partition "part1" {
			acl="write"
		}
		`,
	}
	pol, _, err := client.ACL().PolicyCreate(partPol, nil)
	if err != nil {
		t.Fatal(err)
	}

	// Create new Token in default partition with new ACL
	cToken, _, err := client.ACL().TokenCreate(
		&consulapi.ACLToken{
			Policies: []*consulapi.ACLLink{{ID: pol.ID}},
		}, nil)
	if err != nil {
		t.Fatal(err)
	}

	// Write backend config
	connData := map[string]interface{}{
		"address": consulConfig.Address(),
		"token":   cToken.SecretID,
	}

	req := &logical.Request{
		Storage:   config.StorageView,
		Operation: logical.UpdateOperation,
		Path:      "config/access",
		Data:      connData,
	}
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	// Create the role in partition "part1"
	req.Path = "roles/test-part"
	req.Data = map[string]interface{}{
		"consul_policies": []string{"part-test"},
		"lease":           "6h",
		"partition":       "part1",
	}
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	// Get Token
	req.Operation = logical.ReadOperation
	req.Path = "creds/test-part"
	resp, err := b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatal("resp nil")
	}
	if resp.IsError() {
		t.Fatalf("resp is error: %v", resp.Error())
	}

	generatedSecret := resp.Secret
	generatedSecret.TTL = 6 * time.Hour

	// Verify Secret
	var d struct {
		Token     string `mapstructure:"token"`
		Accessor  string `mapstructure:"accessor"`
		Partition string `mapstructure:"partition"`
	}
	if err := mapstructure.Decode(resp.Data, &d); err != nil {
		t.Fatal(err)
	}

	if d.Partition != "part1" {
		t.Fatalf("Failed to access partition")
	}

	// Revoke the credential
	req.Operation = logical.RevokeOperation
	req.Secret = generatedSecret
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatalf("Revocation failed: %v", err)
	}

	// Build a management client and verify that the token does not exist anymore
	consulmgmtConfig := consulapi.DefaultNonPooledConfig()
	consulmgmtConfig.Address = connData["address"].(string)
	consulmgmtConfig.Token = connData["token"].(string)
	mgmtclient, err := consulapi.NewClient(consulmgmtConfig)
	if err != nil {
		t.Fatal(err)
	}
	q := &consulapi.QueryOptions{
		Datacenter: "DC1",
		Partition:  "part1",
	}

	_, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q)
	if err == nil {
		t.Fatal("err: expected error")
	}
}

func testBackendEntNamespace(t *testing.T) {
	config := logical.TestBackendConfig()
	config.StorageView = &logical.InmemStorage{}
	b, err := Factory(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	cleanup, consulConfig := consul.PrepareTestContainer(t, "", true, true)
	defer cleanup()

	connData := map[string]interface{}{
		"address": consulConfig.Address(),
		"token":   consulConfig.Token,
	}

	req := &logical.Request{
		Storage:   config.StorageView,
		Operation: logical.UpdateOperation,
		Path:      "config/access",
		Data:      connData,
	}
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	// Create the role in namespace "ns1"
	req.Path = "roles/test-ns"
	req.Data = map[string]interface{}{
		"consul_policies":  []string{"ns-test"},
		"lease":            "6h",
		"consul_namespace": "ns1",
	}
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	req.Operation = logical.ReadOperation
	req.Path = "creds/test-ns"
	resp, err := b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatal("resp nil")
	}
	if resp.IsError() {
		t.Fatalf("resp is error: %v", resp.Error())
	}

	generatedSecret := resp.Secret
	generatedSecret.TTL = 6 * time.Hour

	var d struct {
		Token           string `mapstructure:"token"`
		Accessor        string `mapstructure:"accessor"`
		ConsulNamespace string `mapstructure:"consul_namespace"`
	}
	if err := mapstructure.Decode(resp.Data, &d); err != nil {
		t.Fatal(err)
	}

	if d.ConsulNamespace != "ns1" {
		t.Fatalf("Failed to access namespace")
	}

	// Build a client and verify that the credentials work
	consulapiConfig := consulapi.DefaultNonPooledConfig()
	consulapiConfig.Address = connData["address"].(string)
	consulapiConfig.Token = d.Token
	client, err := consulapi.NewClient(consulapiConfig)
	if err != nil {
		t.Fatal(err)
	}

	_, err = client.Catalog(), nil
	if err != nil {
		t.Fatal(err)
	}

	req.Operation = logical.RenewOperation
	req.Secret = generatedSecret
	resp, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatal("got nil response from renew")
	}

	req.Operation = logical.RevokeOperation
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	// Build a management client and verify that the token does not exist anymore
	consulmgmtConfig := consulapi.DefaultNonPooledConfig()
	consulmgmtConfig.Address = connData["address"].(string)
	consulmgmtConfig.Token = connData["token"].(string)
	mgmtclient, err := consulapi.NewClient(consulmgmtConfig)
	if err != nil {
		t.Fatal(err)
	}
	q := &consulapi.QueryOptions{
		Datacenter: "DC1",
		Namespace:  "ns1",
	}

	_, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q)
	if err == nil {
		t.Fatal("err: expected error")
	}
}

func testBackendEntPartition(t *testing.T) {
	config := logical.TestBackendConfig()
	config.StorageView = &logical.InmemStorage{}
	b, err := Factory(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	cleanup, consulConfig := consul.PrepareTestContainer(t, "", true, true)
	defer cleanup()

	connData := map[string]interface{}{
		"address": consulConfig.Address(),
		"token":   consulConfig.Token,
	}

	req := &logical.Request{
		Storage:   config.StorageView,
		Operation: logical.UpdateOperation,
		Path:      "config/access",
		Data:      connData,
	}
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	// Create the role in partition "part1"
	req.Path = "roles/test-part"
	req.Data = map[string]interface{}{
		"consul_policies": []string{"part-test"},
		"lease":           "6h",
		"partition":       "part1",
	}
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	req.Operation = logical.ReadOperation
	req.Path = "creds/test-part"
	resp, err := b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatal("resp nil")
	}
	if resp.IsError() {
		t.Fatalf("resp is error: %v", resp.Error())
	}

	generatedSecret := resp.Secret
	generatedSecret.TTL = 6 * time.Hour

	var d struct {
		Token     string `mapstructure:"token"`
		Accessor  string `mapstructure:"accessor"`
		Partition string `mapstructure:"partition"`
	}
	if err := mapstructure.Decode(resp.Data, &d); err != nil {
		t.Fatal(err)
	}

	if d.Partition != "part1" {
		t.Fatalf("Failed to access partition")
	}

	// Build a client and verify that the credentials work
	consulapiConfig := consulapi.DefaultNonPooledConfig()
	consulapiConfig.Address = connData["address"].(string)
	consulapiConfig.Token = d.Token
	client, err := consulapi.NewClient(consulapiConfig)
	if err != nil {
		t.Fatal(err)
	}

	_, err = client.Catalog(), nil
	if err != nil {
		t.Fatal(err)
	}

	req.Operation = logical.RenewOperation
	req.Secret = generatedSecret
	resp, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatal("got nil response from renew")
	}

	req.Operation = logical.RevokeOperation
	_, err = b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	// Build a management client and verify that the token does not exist anymore
	consulmgmtConfig := consulapi.DefaultNonPooledConfig()
	consulmgmtConfig.Address = connData["address"].(string)
	consulmgmtConfig.Token = connData["token"].(string)
	mgmtclient, err := consulapi.NewClient(consulmgmtConfig)
	if err != nil {
		t.Fatal(err)
	}
	q := &consulapi.QueryOptions{
		Datacenter: "DC1",
		Partition:  "test1",
	}

	_, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q)
	if err == nil {
		t.Fatal("err: expected error")
	}
}

func TestBackendRenewRevokeRolesAndIdentities(t *testing.T) {
	config := logical.TestBackendConfig()
	config.StorageView = &logical.InmemStorage{}
	b, err := Factory(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	cleanup, consulConfig := consul.PrepareTestContainer(t, "", false, true)
	defer cleanup()

	connData := map[string]interface{}{
		"address": consulConfig.Address(),
		"token":   consulConfig.Token,
	}

	req := &logical.Request{
		Storage:   config.StorageView,
		Operation: logical.UpdateOperation,
		Path:      "config/access",
		Data:      connData,
	}
	resp, err := b.HandleRequest(context.Background(), req)
	if err != nil {
		t.Fatal(err)
	}

	cases := map[string]struct {
		RoleName string
		RoleData map[string]interface{}
	}{
		"just role": {
			"r",
			map[string]interface{}{
				"consul_roles": []string{"role-test"},
				"lease":        "6h",
			},
		},
		"role and policies": {
			"rp",
			map[string]interface{}{
				"consul_policies": []string{"test"},
				"consul_roles":    []string{"role-test"},
				"lease":           "6h",
			},
		},
		"service identity": {
			"si",
			map[string]interface{}{
				"service_identities": "service1",
				"lease":              "6h",
			},
		},
		"service identity and policies": {
			"sip",
			map[string]interface{}{
				"consul_policies":    []string{"test"},
				"service_identities": "service1",
				"lease":              "6h",
			},
		},
		"service identity and role": {
			"sir",
			map[string]interface{}{
				"consul_roles":       []string{"role-test"},
				"service_identities": "service1",
				"lease":              "6h",
			},
		},
		"service identity and role and policies": {
			"sirp",
			map[string]interface{}{
				"consul_policies":    []string{"test"},
				"consul_roles":       []string{"role-test"},
				"service_identities": "service1",
				"lease":              "6h",
			},
		},
		"node identity": {
			"ni",
			map[string]interface{}{
				"node_identities": []string{"node1:dc1"},
				"lease":           "6h",
			},
		},
		"node identity and policies": {
			"nip",
			map[string]interface{}{
				"consul_policies": []string{"test"},
				"node_identities": []string{"node1:dc1"},
				"lease":           "6h",
			},
		},
		"node identity and role": {
			"nir",
			map[string]interface{}{
				"consul_roles":    []string{"role-test"},
				"node_identities": []string{"node1:dc1"},
				"lease":           "6h",
			},
		},
		"node identity and role and policies": {
			"nirp",
			map[string]interface{}{
				"consul_policies": []string{"test"},
				"consul_roles":    []string{"role-test"},
				"node_identities": []string{"node1:dc1"},
				"lease":           "6h",
			},
		},
		"node identity and service identity": {
			"nisi",
			map[string]interface{}{
				"service_identities": "service1",
				"node_identities":    []string{"node1:dc1"},
				"lease":              "6h",
			},
		},
		"node identity and service identity and policies": {
			"nisip",
			map[string]interface{}{
				"consul_policies":    []string{"test"},
				"service_identities": "service1",
				"node_identities":    []string{"node1:dc1"},
				"lease":              "6h",
			},
		},
		"node identity and service identity and role": {
			"nisir",
			map[string]interface{}{
				"consul_roles":       []string{"role-test"},
				"service_identities": "service1",
				"node_identities":    []string{"node1:dc1"},
				"lease":              "6h",
			},
		},
		"node identity and service identity and role and policies": {
			"nisirp",
			map[string]interface{}{
				"consul_policies":    []string{"test"},
				"consul_roles":       []string{"role-test"},
				"service_identities": "service1",
				"node_identities":    []string{"node1:dc1"},
				"lease":              "6h",
			},
		},
	}

	for description, tc := range cases {
		t.Logf("Testing: %s", description)

		req.Operation = logical.UpdateOperation
		req.Path = fmt.Sprintf("roles/%s", tc.RoleName)
		req.Data = tc.RoleData
		resp, err = b.HandleRequest(context.Background(), req)
		if err != nil {
			t.Fatal(err)
		}

		req.Operation = logical.ReadOperation
		req.Path = fmt.Sprintf("creds/%s", tc.RoleName)
		resp, err = b.HandleRequest(context.Background(), req)
		if err != nil {
			t.Fatal(err)
		}
		if resp == nil {
			t.Fatal("resp nil")
		}
		if resp.IsError() {
			t.Fatalf("resp is error: %v", resp.Error())
		}

		generatedSecret := resp.Secret
		generatedSecret.TTL = 6 * time.Hour

		var d struct {
			Token    string `mapstructure:"token"`
			Accessor string `mapstructure:"accessor"`
		}
		if err := mapstructure.Decode(resp.Data, &d); err != nil {
			t.Fatal(err)
		}

		// Build a client and verify that the credentials work
		consulapiConfig := consulapi.DefaultNonPooledConfig()
		consulapiConfig.Address = connData["address"].(string)
		consulapiConfig.Token = d.Token
		client, err := consulapi.NewClient(consulapiConfig)
		if err != nil {
			t.Fatal(err)
		}

		_, err = client.Catalog(), nil
		if err != nil {
			t.Fatal(err)
		}

		req.Operation = logical.RenewOperation
		req.Secret = generatedSecret
		resp, err = b.HandleRequest(context.Background(), req)
		if err != nil {
			t.Fatal(err)
		}
		if resp == nil {
			t.Fatal("got nil response from renew")
		}

		req.Operation = logical.RevokeOperation
		resp, err = b.HandleRequest(context.Background(), req)
		if err != nil {
			t.Fatal(err)
		}

		// Build a management client and verify that the token does not exist anymore
		consulmgmtConfig := consulapi.DefaultNonPooledConfig()
		consulmgmtConfig.Address = connData["address"].(string)
		consulmgmtConfig.Token = connData["token"].(string)
		mgmtclient, err := consulapi.NewClient(consulmgmtConfig)

		q := &consulapi.QueryOptions{
			Datacenter: "DC1",
		}

		_, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q)
		if err == nil {
			t.Fatal("err: expected error")
		}
	}
}

const testPolicy = `
key "" {
	policy = "write"
}`
