package structs

import (
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	"hash"
	"hash/fnv"
	"sort"
	"strings"
	"time"

	"github.com/hashicorp/consul/acl"
	"github.com/hashicorp/consul/lib"
	"golang.org/x/crypto/blake2b"
)

type ACLMode string

const (
	// ACLs are disabled by configuration
	ACLModeDisabled ACLMode = "0"
	// ACLs are enabled
	ACLModeEnabled ACLMode = "1"
	// DEPRECATED (ACL-Legacy-Compat) - only needed while legacy ACLs are supported
	// ACLs are enabled and using legacy ACLs
	ACLModeLegacy ACLMode = "2"
	// DEPRECATED (ACL-Legacy-Compat) - only needed while legacy ACLs are supported
	// ACLs are assumed enabled but not being advertised
	ACLModeUnknown ACLMode = "3"
)

// ACLOp is used in RPCs to encode ACL operations.
type ACLOp string

type ACLTokenIDType string

const (
	ACLTokenSecret   ACLTokenIDType = "secret"
	ACLTokenAccessor ACLTokenIDType = "accessor"
)

type ACLPolicyIDType string

const (
	ACLPolicyName ACLPolicyIDType = "name"
	ACLPolicyID   ACLPolicyIDType = "id"
)

const (
	// All policy ids with the first 120 bits set to all zeroes are
	// reserved for builtin policies. Policy creation will ensure we
	// dont accidentally create them when autogenerating uuids.

	// This policy gives unlimited access to everything. Users
	// may rename if desired but cannot delete or modify the rules.
	ACLPolicyGlobalManagementID = "00000000-0000-0000-0000-000000000001"
	ACLPolicyGlobalManagement   = `
acl = "write"
agent_prefix "" {
	policy = "write"
}
event_prefix "" {
	policy = "write"
}
key_prefix "" {
	policy = "write"
}
keyring = "write"
node_prefix "" {
	policy = "write"
}
operator = "write"
query_prefix "" {
	policy = "write"
}
service_prefix "" {
	policy = "write"
	intentions = "write"
}
session_prefix "" {
	policy = "write"
}` + EnterpriseACLPolicyGlobalManagement

	// This is the policy ID for anonymous access. This is configurable by the
	// user.
	ACLTokenAnonymousID = "00000000-0000-0000-0000-000000000002"

	ACLReservedPrefix = "00000000-0000-0000-0000-0000000000"
)

func ACLIDReserved(id string) bool {
	return strings.HasPrefix(id, ACLReservedPrefix)
}

const (
	// ACLSet creates or updates a token.
	ACLSet ACLOp = "set"

	// ACLDelete deletes a token.
	ACLDelete ACLOp = "delete"
)

// ACLBootstrapNotAllowedErr is returned once we know that a bootstrap can no
// longer be done since the cluster was bootstrapped
var ACLBootstrapNotAllowedErr = errors.New("ACL bootstrap no longer allowed")

// ACLBootstrapInvalidResetIndexErr is returned when bootstrap is requested with a non-zero
// reset index but the index doesn't match the bootstrap index
var ACLBootstrapInvalidResetIndexErr = errors.New("Invalid ACL bootstrap reset index")

type ACLIdentity interface {
	// ID returns a string that can be used for logging and telemetry. This should not
	// contain any secret data used for authentication
	ID() string
	SecretToken() string
	PolicyIDs() []string
	RoleIDs() []string
	EmbeddedPolicy() *ACLPolicy
	ServiceIdentityList() []*ACLServiceIdentity
	NodeIdentityList() []*ACLNodeIdentity
	IsExpired(asOf time.Time) bool
	IsLocal() bool
	EnterpriseMetadata() *EnterpriseMeta
}

type ACLTokenPolicyLink struct {
	ID   string
	Name string `hash:"ignore"`
}

type ACLTokenRoleLink struct {
	ID   string
	Name string `hash:"ignore"`
}

// ACLServiceIdentity represents a high-level grant of all necessary privileges
// to assume the identity of the named Service in the Catalog and within
// Connect.
type ACLServiceIdentity struct {
	ServiceName string

	// Datacenters that the synthetic policy will be valid within.
	//   - No wildcards allowed
	//   - If empty then the synthetic policy is valid within all datacenters
	//
	// Only valid for global tokens. It is an error to specify this for local tokens.
	Datacenters []string `json:",omitempty"`
}

func (s *ACLServiceIdentity) Clone() *ACLServiceIdentity {
	s2 := *s
	s2.Datacenters = CloneStringSlice(s.Datacenters)
	return &s2
}

func (s *ACLServiceIdentity) AddToHash(h hash.Hash) {
	h.Write([]byte(s.ServiceName))
	for _, dc := range s.Datacenters {
		h.Write([]byte(dc))
	}
}

func (s *ACLServiceIdentity) EstimateSize() int {
	size := len(s.ServiceName)
	for _, dc := range s.Datacenters {
		size += len(dc)
	}
	return size
}

func (s *ACLServiceIdentity) SyntheticPolicy(entMeta *EnterpriseMeta) *ACLPolicy {
	// Given that we validate this string name before persisting, we do not
	// have to escape it before doing the following interpolation.
	rules := aclServiceIdentityRules(s.ServiceName, entMeta)

	hasher := fnv.New128a()
	hashID := fmt.Sprintf("%x", hasher.Sum([]byte(rules)))

	policy := &ACLPolicy{}
	policy.ID = hashID
	policy.Name = fmt.Sprintf("synthetic-policy-%s", hashID)
	policy.Description = "synthetic policy"
	policy.Rules = rules
	policy.Syntax = acl.SyntaxCurrent
	policy.Datacenters = s.Datacenters
	policy.EnterpriseMeta.Merge(entMeta)
	policy.SetHash(true)
	return policy
}

// ACLNodeIdentity represents a high-level grant of all privileges
// necessary to assume the identity of that node and manage it.
type ACLNodeIdentity struct {
	// NodeName identities the Node that this identity authorizes access to
	NodeName string

	// Datacenter is required and specifies the datacenter of the node.
	Datacenter string
}

func (s *ACLNodeIdentity) Clone() *ACLNodeIdentity {
	s2 := *s
	return &s2
}

func (s *ACLNodeIdentity) AddToHash(h hash.Hash) {
	h.Write([]byte(s.NodeName))
	h.Write([]byte(s.Datacenter))
}

func (s *ACLNodeIdentity) EstimateSize() int {
	return len(s.NodeName) + len(s.Datacenter)
}

func (s *ACLNodeIdentity) SyntheticPolicy() *ACLPolicy {
	// Given that we validate this string name before persisting, we do not
	// have to escape it before doing the following interpolation.
	rules := fmt.Sprintf(aclPolicyTemplateNodeIdentity, s.NodeName)

	hasher := fnv.New128a()
	hashID := fmt.Sprintf("%x", hasher.Sum([]byte(rules)))

	policy := &ACLPolicy{}
	policy.ID = hashID
	policy.Name = fmt.Sprintf("synthetic-policy-%s", hashID)
	policy.Description = "synthetic policy"
	policy.Rules = rules
	policy.Syntax = acl.SyntaxCurrent
	policy.Datacenters = []string{s.Datacenter}
	policy.EnterpriseMeta = *DefaultEnterpriseMeta()
	policy.SetHash(true)
	return policy
}

type ACLToken struct {
	// This is the UUID used for tracking and management purposes
	AccessorID string

	// This is the UUID used as the api token by clients
	SecretID string

	// Human readable string to display for the token (Optional)
	Description string

	// List of policy links - nil/empty for legacy tokens or if service identities are in use.
	// Note this is the list of IDs and not the names. Prior to token creation
	// the list of policy names gets validated and the policy IDs get stored herein
	Policies []ACLTokenPolicyLink `json:",omitempty"`

	// List of role links. Note this is the list of IDs and not the names.
	// Prior to token creation the list of role names gets validated and the
	// role IDs get stored herein
	Roles []ACLTokenRoleLink `json:",omitempty"`

	// List of services to generate synthetic policies for.
	ServiceIdentities []*ACLServiceIdentity `json:",omitempty"`

	// The node identities that this token should be allowed to manage.
	NodeIdentities []*ACLNodeIdentity `json:",omitempty"`

	// Type is the V1 Token Type
	// DEPRECATED (ACL-Legacy-Compat) - remove once we no longer support v1 ACL compat
	// Even though we are going to auto upgrade management tokens we still
	// want to be able to have the old APIs operate on the upgraded management tokens
	// so this field is being kept to identify legacy tokens even after an auto-upgrade
	Type string `json:"-"`

	// Rules is the V1 acl rules associated with
	// DEPRECATED (ACL-Legacy-Compat) - remove once we no longer support v1 ACL compat
	Rules string `json:",omitempty"`

	// Whether this token is DC local. This means that it will not be synced
	// to the ACL datacenter and replicated to others.
	Local bool

	// AuthMethod is the name of the auth method used to create this token.
	AuthMethod string `json:",omitempty"`

	// ACLAuthMethodEnterpriseMeta is the EnterpriseMeta for the AuthMethod that this token was created from
	ACLAuthMethodEnterpriseMeta

	// ExpirationTime represents the point after which a token should be
	// considered revoked and is eligible for destruction. The zero value
	// represents NO expiration.
	//
	// This is a pointer value so that the zero value is omitted properly
	// during json serialization. time.Time does not respect json omitempty
	// directives unfortunately.
	ExpirationTime *time.Time `json:",omitempty"`

	// ExpirationTTL is a convenience field for helping set ExpirationTime to a
	// value of CreateTime+ExpirationTTL. This can only be set during
	// TokenCreate and is cleared and used to initialize the ExpirationTime
	// field before being persisted to the state store or raft log.
	//
	// This is a string version of a time.Duration like "2m".
	ExpirationTTL time.Duration `json:",omitempty"`

	// The time when this token was created
	CreateTime time.Time `json:",omitempty"`

	// Hash of the contents of the token
	//
	// This is needed mainly for replication purposes. When replicating from
	// one DC to another keeping the content Hash will allow us to avoid
	// unnecessary calls to the authoritative DC
	Hash []byte

	// Embedded Enterprise Metadata
	EnterpriseMeta `mapstructure:",squash"`

	// Embedded Raft Metadata
	RaftIndex
}

func (t *ACLToken) UnmarshalJSON(data []byte) (err error) {
	type Alias ACLToken
	aux := &struct {
		ExpirationTTL interface{}
		Hash          string
		*Alias
	}{
		Alias: (*Alias)(t),
	}

	if err = lib.UnmarshalJSON(data, &aux); err != nil {
		return err
	}
	if aux.ExpirationTTL != nil {
		switch v := aux.ExpirationTTL.(type) {
		case string:
			if t.ExpirationTTL, err = time.ParseDuration(v); err != nil {
				return err
			}
		case float64:
			t.ExpirationTTL = time.Duration(v)
		}

	}
	if aux.Hash != "" {
		t.Hash = []byte(aux.Hash)
	}
	return nil
}

func (t *ACLToken) Clone() *ACLToken {
	t2 := *t
	t2.Policies = nil
	t2.Roles = nil
	t2.ServiceIdentities = nil
	t2.NodeIdentities = nil

	if len(t.Policies) > 0 {
		t2.Policies = make([]ACLTokenPolicyLink, len(t.Policies))
		copy(t2.Policies, t.Policies)
	}
	if len(t.Roles) > 0 {
		t2.Roles = make([]ACLTokenRoleLink, len(t.Roles))
		copy(t2.Roles, t.Roles)
	}
	if len(t.ServiceIdentities) > 0 {
		t2.ServiceIdentities = make([]*ACLServiceIdentity, len(t.ServiceIdentities))
		for i, s := range t.ServiceIdentities {
			t2.ServiceIdentities[i] = s.Clone()
		}
	}
	if len(t.NodeIdentities) > 0 {
		t2.NodeIdentities = make([]*ACLNodeIdentity, len(t.NodeIdentities))
		for i, n := range t.NodeIdentities {
			t2.NodeIdentities[i] = n.Clone()
		}
	}

	return &t2
}

func (t *ACLToken) ID() string {
	return t.AccessorID
}

func (t *ACLToken) SecretToken() string {
	return t.SecretID
}

func (t *ACLToken) PolicyIDs() []string {
	if len(t.Policies) == 0 {
		return nil
	}

	ids := make([]string, 0, len(t.Policies))
	for _, link := range t.Policies {
		ids = append(ids, link.ID)
	}
	return ids
}

func (t *ACLToken) RoleIDs() []string {
	if len(t.Roles) == 0 {
		return nil
	}

	ids := make([]string, 0, len(t.Roles))
	for _, link := range t.Roles {
		ids = append(ids, link.ID)
	}
	return ids
}

func (t *ACLToken) ServiceIdentityList() []*ACLServiceIdentity {
	if len(t.ServiceIdentities) == 0 {
		return nil
	}

	out := make([]*ACLServiceIdentity, 0, len(t.ServiceIdentities))
	for _, s := range t.ServiceIdentities {
		out = append(out, s.Clone())
	}
	return out
}

func (t *ACLToken) IsExpired(asOf time.Time) bool {
	if asOf.IsZero() || !t.HasExpirationTime() {
		return false
	}
	return t.ExpirationTime.Before(asOf)
}

func (t *ACLToken) IsLocal() bool {
	return t.Local
}

func (t *ACLToken) HasExpirationTime() bool {
	return t.ExpirationTime != nil && !t.ExpirationTime.IsZero()
}

func (t *ACLToken) UsesNonLegacyFields() bool {
	return len(t.Policies) > 0 ||
		len(t.ServiceIdentities) > 0 ||
		len(t.NodeIdentities) > 0 ||
		len(t.Roles) > 0 ||
		t.Type == "" ||
		t.HasExpirationTime() ||
		t.ExpirationTTL != 0 ||
		t.AuthMethod != ""
}

func (t *ACLToken) EmbeddedPolicy() *ACLPolicy {
	// DEPRECATED (ACL-Legacy-Compat)
	//
	// For legacy tokens with embedded rules this provides a way to map those
	// rules to an ACLPolicy. This function can just return nil once legacy
	// acl compatibility is no longer needed.
	//
	// Additionally for management tokens we must embed the policy rules
	// as well
	policy := &ACLPolicy{}
	if t.Type == ACLTokenTypeManagement {
		hasher := fnv.New128a()
		policy.ID = fmt.Sprintf("%x", hasher.Sum([]byte(ACLPolicyGlobalManagement)))
		policy.Name = "legacy-management"
		policy.Rules = ACLPolicyGlobalManagement
		policy.Syntax = acl.SyntaxCurrent
	} else if t.Rules != "" || t.Type == ACLTokenTypeClient {
		hasher := fnv.New128a()
		policy.ID = fmt.Sprintf("%x", hasher.Sum([]byte(t.Rules)))
		policy.Name = fmt.Sprintf("legacy-policy-%s", policy.ID)
		policy.Rules = t.Rules
		policy.Syntax = acl.SyntaxLegacy
	} else {
		return nil
	}

	policy.SetHash(true)
	return policy
}

func (t *ACLToken) EnterpriseMetadata() *EnterpriseMeta {
	return &t.EnterpriseMeta
}

func (t *ACLToken) SetHash(force bool) []byte {
	if force || t.Hash == nil {
		// Initialize a 256bit Blake2 hash (32 bytes)
		hash, err := blake2b.New256(nil)
		if err != nil {
			panic(err)
		}

		// Any non-immutable "content" fields should be involved with the
		// overall hash. The IDs are immutable which is why they aren't here.
		// The raft indices are metadata similar to the hash which is why they
		// aren't incorporated. CreateTime is similarly immutable
		//
		// The Hash is really only used for replication to determine if a token
		// has changed and should be updated locally.

		// Write all the user set fields
		hash.Write([]byte(t.Description))
		hash.Write([]byte(t.Type))
		hash.Write([]byte(t.Rules))

		if t.Local {
			hash.Write([]byte("local"))
		} else {
			hash.Write([]byte("global"))
		}

		for _, link := range t.Policies {
			hash.Write([]byte(link.ID))
		}

		for _, link := range t.Roles {
			hash.Write([]byte(link.ID))
		}

		for _, srvid := range t.ServiceIdentities {
			srvid.AddToHash(hash)
		}

		for _, nodeID := range t.NodeIdentities {
			nodeID.AddToHash(hash)
		}

		t.EnterpriseMeta.addToHash(hash, false)

		// Finalize the hash
		hashVal := hash.Sum(nil)

		// Set and return the hash
		t.Hash = hashVal
	}
	return t.Hash
}

func (t *ACLToken) EstimateSize() int {
	// 41 = 16 (RaftIndex) + 8 (Hash) + 8 (ExpirationTime) + 8 (CreateTime) + 1 (Local)
	size := 41 + len(t.AccessorID) + len(t.SecretID) + len(t.Description) + len(t.Type) + len(t.Rules) + len(t.AuthMethod)
	for _, link := range t.Policies {
		size += len(link.ID) + len(link.Name)
	}
	for _, link := range t.Roles {
		size += len(link.ID) + len(link.Name)
	}
	for _, srvid := range t.ServiceIdentities {
		size += srvid.EstimateSize()
	}
	for _, nodeID := range t.NodeIdentities {
		size += nodeID.EstimateSize()
	}
	return size + t.EnterpriseMeta.estimateSize()
}

// ACLTokens is a slice of ACLTokens.
type ACLTokens []*ACLToken

type ACLTokenListStub struct {
	AccessorID        string
	Description       string
	Policies          []ACLTokenPolicyLink  `json:",omitempty"`
	Roles             []ACLTokenRoleLink    `json:",omitempty"`
	ServiceIdentities []*ACLServiceIdentity `json:",omitempty"`
	NodeIdentities    []*ACLNodeIdentity    `json:",omitempty"`
	Local             bool
	AuthMethod        string     `json:",omitempty"`
	ExpirationTime    *time.Time `json:",omitempty"`
	CreateTime        time.Time  `json:",omitempty"`
	Hash              []byte
	CreateIndex       uint64
	ModifyIndex       uint64
	Legacy            bool `json:",omitempty"`
	EnterpriseMeta
}

type ACLTokenListStubs []*ACLTokenListStub

func (token *ACLToken) Stub() *ACLTokenListStub {
	return &ACLTokenListStub{
		AccessorID:        token.AccessorID,
		Description:       token.Description,
		Policies:          token.Policies,
		Roles:             token.Roles,
		ServiceIdentities: token.ServiceIdentities,
		NodeIdentities:    token.NodeIdentities,
		Local:             token.Local,
		AuthMethod:        token.AuthMethod,
		ExpirationTime:    token.ExpirationTime,
		CreateTime:        token.CreateTime,
		Hash:              token.Hash,
		CreateIndex:       token.CreateIndex,
		ModifyIndex:       token.ModifyIndex,
		Legacy:            token.Rules != "",
		EnterpriseMeta:    token.EnterpriseMeta,
	}
}

func (tokens ACLTokens) Sort() {
	sort.Slice(tokens, func(i, j int) bool {
		return tokens[i].AccessorID < tokens[j].AccessorID
	})
}

func (tokens ACLTokenListStubs) Sort() {
	sort.Slice(tokens, func(i, j int) bool {
		return tokens[i].AccessorID < tokens[j].AccessorID
	})
}

type ACLPolicy struct {
	// This is the internal UUID associated with the policy
	ID string

	// Unique name to reference the policy by.
	//   - Valid Characters: [a-zA-Z0-9-]
	//   - Valid Lengths: 1 - 128
	Name string

	// Human readable description (Optional)
	Description string

	// The rule set (using the updated rule syntax)
	Rules string

	// DEPRECATED (ACL-Legacy-Compat) - This is only needed while we support the legacy ACLs
	Syntax acl.SyntaxVersion `json:"-"`

	// Datacenters that the policy is valid within.
	//   - No wildcards allowed
	//   - If empty then the policy is valid within all datacenters
	Datacenters []string `json:",omitempty"`

	// Hash of the contents of the policy
	// This does not take into account the ID (which is immutable)
	// nor the raft metadata.
	//
	// This is needed mainly for replication purposes. When replicating from
	// one DC to another keeping the content Hash will allow us to avoid
	// unnecessary calls to the authoritative DC
	Hash []byte

	// Embedded Enterprise ACL Metadata
	EnterpriseMeta `mapstructure:",squash"`

	// Embedded Raft Metadata
	RaftIndex `hash:"ignore"`
}

func (t *ACLPolicy) UnmarshalJSON(data []byte) error {
	type Alias ACLPolicy
	aux := &struct {
		Hash string
		*Alias
	}{
		Alias: (*Alias)(t),
	}

	if err := lib.UnmarshalJSON(data, &aux); err != nil {
		return err
	}
	if aux.Hash != "" {
		t.Hash = []byte(aux.Hash)
	}
	return nil
}

func (p *ACLPolicy) Clone() *ACLPolicy {
	p2 := *p
	p2.Datacenters = CloneStringSlice(p.Datacenters)
	return &p2
}

type ACLPolicyListStub struct {
	ID          string
	Name        string
	Description string
	Datacenters []string
	Hash        []byte
	CreateIndex uint64
	ModifyIndex uint64
	EnterpriseMeta
}

func (p *ACLPolicy) Stub() *ACLPolicyListStub {
	return &ACLPolicyListStub{
		ID:             p.ID,
		Name:           p.Name,
		Description:    p.Description,
		Datacenters:    p.Datacenters,
		Hash:           p.Hash,
		CreateIndex:    p.CreateIndex,
		ModifyIndex:    p.ModifyIndex,
		EnterpriseMeta: p.EnterpriseMeta,
	}
}

type ACLPolicies []*ACLPolicy
type ACLPolicyListStubs []*ACLPolicyListStub

func (p *ACLPolicy) SetHash(force bool) []byte {
	if force || p.Hash == nil {
		// Initialize a 256bit Blake2 hash (32 bytes)
		hash, err := blake2b.New256(nil)
		if err != nil {
			panic(err)
		}

		// Any non-immutable "content" fields should be involved with the
		// overall hash. The ID is immutable which is why it isn't here.  The
		// raft indices are metadata similar to the hash which is why they
		// aren't incorporated. CreateTime is similarly immutable
		//
		// The Hash is really only used for replication to determine if a policy
		// has changed and should be updated locally.

		// Write all the user set fields
		hash.Write([]byte(p.Name))
		hash.Write([]byte(p.Description))
		hash.Write([]byte(p.Rules))
		for _, dc := range p.Datacenters {
			hash.Write([]byte(dc))
		}

		p.EnterpriseMeta.addToHash(hash, false)

		// Finalize the hash
		hashVal := hash.Sum(nil)

		// Set and return the hash
		p.Hash = hashVal
	}
	return p.Hash
}

func (p *ACLPolicy) EstimateSize() int {
	// This is just an estimate. There is other data structure overhead
	// pointers etc that this does not account for.

	// 64 = 36 (uuid) + 16 (RaftIndex) + 8 (Hash) + 4 (Syntax)
	size := 64 + len(p.Name) + len(p.Description) + len(p.Rules)
	for _, dc := range p.Datacenters {
		size += len(dc)
	}

	return size + p.EnterpriseMeta.estimateSize()
}

// HashKey returns a consistent hash for a set of policies.
func (policies ACLPolicies) HashKey() string {
	cacheKeyHash, err := blake2b.New256(nil)
	if err != nil {
		panic(err)
	}
	for _, policy := range policies {
		cacheKeyHash.Write([]byte(policy.ID))
		// including the modify index prevents a policy set from being
		// cached if one of the policies has changed
		binary.Write(cacheKeyHash, binary.BigEndian, policy.ModifyIndex)
	}
	return fmt.Sprintf("%x", cacheKeyHash.Sum(nil))
}

func (policies ACLPolicies) Sort() {
	sort.Slice(policies, func(i, j int) bool {
		return policies[i].ID < policies[j].ID
	})
}

func (policies ACLPolicyListStubs) Sort() {
	sort.Slice(policies, func(i, j int) bool {
		return policies[i].ID < policies[j].ID
	})
}

func (policies ACLPolicies) resolveWithCache(cache *ACLCaches, entConf *acl.Config) ([]*acl.Policy, error) {
	// Parse the policies
	parsed := make([]*acl.Policy, 0, len(policies))
	for _, policy := range policies {
		policy.SetHash(false)
		cacheKey := fmt.Sprintf("%x", policy.Hash)
		cachedPolicy := cache.GetParsedPolicy(cacheKey)
		if cachedPolicy != nil {
			// policies are content hashed so no need to check the age
			parsed = append(parsed, cachedPolicy.Policy)
			continue
		}

		p, err := acl.NewPolicyFromSource(policy.ID, policy.ModifyIndex, policy.Rules, policy.Syntax, entConf, policy.EnterprisePolicyMeta())
		if err != nil {
			return nil, fmt.Errorf("failed to parse %q: %v", policy.Name, err)
		}

		cache.PutParsedPolicy(cacheKey, p)
		parsed = append(parsed, p)
	}

	return parsed, nil
}

func (policies ACLPolicies) Compile(cache *ACLCaches, entConf *acl.Config) (acl.Authorizer, error) {
	// Determine the cache key
	cacheKey := policies.HashKey()
	entry := cache.GetAuthorizer(cacheKey)
	if entry != nil {
		// the hash key takes into account the policy contents. There is no reason to expire this cache or check its age.
		return entry.Authorizer, nil
	}

	parsed, err := policies.resolveWithCache(cache, entConf)
	if err != nil {
		return nil, fmt.Errorf("failed to parse the ACL policies: %v", err)
	}

	// Create the ACL object
	authorizer, err := acl.NewPolicyAuthorizer(parsed, entConf)
	if err != nil {
		return nil, fmt.Errorf("failed to construct ACL Authorizer: %v", err)
	}

	// Update the cache
	cache.PutAuthorizer(cacheKey, authorizer)
	return authorizer, nil
}

func (policies ACLPolicies) Merge(cache *ACLCaches, entConf *acl.Config) (*acl.Policy, error) {
	parsed, err := policies.resolveWithCache(cache, entConf)
	if err != nil {
		return nil, err
	}

	return acl.MergePolicies(parsed), nil
}

type ACLRoles []*ACLRole

// HashKey returns a consistent hash for a set of roles.
func (roles ACLRoles) HashKey() string {
	cacheKeyHash, err := blake2b.New256(nil)
	if err != nil {
		panic(err)
	}
	for _, role := range roles {
		cacheKeyHash.Write([]byte(role.ID))
		// including the modify index prevents a role set from being
		// cached if one of the roles has changed
		binary.Write(cacheKeyHash, binary.BigEndian, role.ModifyIndex)
	}
	return fmt.Sprintf("%x", cacheKeyHash.Sum(nil))
}

func (roles ACLRoles) Sort() {
	sort.Slice(roles, func(i, j int) bool {
		return roles[i].ID < roles[j].ID
	})
}

type ACLRolePolicyLink struct {
	ID   string
	Name string `hash:"ignore"`
}

type ACLRole struct {
	// ID is the internal UUID associated with the role
	ID string

	// Name is the unique name to reference the role by.
	Name string

	// Description is a human readable description (Optional)
	Description string

	// List of policy links.
	// Note this is the list of IDs and not the names. Prior to role creation
	// the list of policy names gets validated and the policy IDs get stored herein
	Policies []ACLRolePolicyLink `json:",omitempty"`

	// List of services to generate synthetic policies for.
	ServiceIdentities []*ACLServiceIdentity `json:",omitempty"`

	// List of nodes to generate synthetic policies for.
	NodeIdentities []*ACLNodeIdentity `json:",omitempty"`

	// Hash of the contents of the role
	// This does not take into account the ID (which is immutable)
	// nor the raft metadata.
	//
	// This is needed mainly for replication purposes. When replicating from
	// one DC to another keeping the content Hash will allow us to avoid
	// unnecessary calls to the authoritative DC
	Hash []byte

	// Embedded Enterprise ACL metadata
	EnterpriseMeta `mapstructure:",squash"`

	// Embedded Raft Metadata
	RaftIndex `hash:"ignore"`
}

func (t *ACLRole) UnmarshalJSON(data []byte) error {
	type Alias ACLRole
	aux := &struct {
		Hash string
		*Alias
	}{
		Alias: (*Alias)(t),
	}

	if err := lib.UnmarshalJSON(data, &aux); err != nil {
		return err
	}
	if aux.Hash != "" {
		t.Hash = []byte(aux.Hash)
	}
	return nil
}

func (r *ACLRole) Clone() *ACLRole {
	r2 := *r
	r2.Policies = nil
	r2.ServiceIdentities = nil
	r2.NodeIdentities = nil

	if len(r.Policies) > 0 {
		r2.Policies = make([]ACLRolePolicyLink, len(r.Policies))
		copy(r2.Policies, r.Policies)
	}
	if len(r.ServiceIdentities) > 0 {
		r2.ServiceIdentities = make([]*ACLServiceIdentity, len(r.ServiceIdentities))
		for i, s := range r.ServiceIdentities {
			r2.ServiceIdentities[i] = s.Clone()
		}
	}
	if len(r.NodeIdentities) > 0 {
		r2.NodeIdentities = make([]*ACLNodeIdentity, len(r.NodeIdentities))
		for i, n := range r.NodeIdentities {
			r2.NodeIdentities[i] = n.Clone()
		}
	}
	return &r2
}

func (r *ACLRole) SetHash(force bool) []byte {
	if force || r.Hash == nil {
		// Initialize a 256bit Blake2 hash (32 bytes)
		hash, err := blake2b.New256(nil)
		if err != nil {
			panic(err)
		}

		// Any non-immutable "content" fields should be involved with the
		// overall hash. The ID is immutable which is why it isn't here.  The
		// raft indices are metadata similar to the hash which is why they
		// aren't incorporated. CreateTime is similarly immutable
		//
		// The Hash is really only used for replication to determine if a role
		// has changed and should be updated locally.

		// Write all the user set fields
		hash.Write([]byte(r.Name))
		hash.Write([]byte(r.Description))
		for _, link := range r.Policies {
			hash.Write([]byte(link.ID))
		}
		for _, srvid := range r.ServiceIdentities {
			srvid.AddToHash(hash)
		}
		for _, nodeID := range r.NodeIdentities {
			nodeID.AddToHash(hash)
		}

		r.EnterpriseMeta.addToHash(hash, false)

		// Finalize the hash
		hashVal := hash.Sum(nil)

		// Set and return the hash
		r.Hash = hashVal
	}
	return r.Hash
}

func (r *ACLRole) EstimateSize() int {
	// This is just an estimate. There is other data structure overhead
	// pointers etc that this does not account for.

	// 60 = 36 (uuid) + 16 (RaftIndex) + 8 (Hash)
	size := 60 + len(r.Name) + len(r.Description)
	for _, link := range r.Policies {
		size += len(link.ID) + len(link.Name)
	}
	for _, srvid := range r.ServiceIdentities {
		size += srvid.EstimateSize()
	}
	for _, nodeID := range r.NodeIdentities {
		size += nodeID.EstimateSize()
	}

	return size + r.EnterpriseMeta.estimateSize()
}

const (
	// BindingRuleBindTypeService is the binding rule bind type that
	// assigns a Service Identity to the token that is created using the value
	// of the computed BindName as the ServiceName like:
	//
	// &ACLToken{
	//   ...other fields...
	//   ServiceIdentities: []*ACLServiceIdentity{
	//     &ACLServiceIdentity{
	//       ServiceName: "<computed BindName>",
	//     },
	//   },
	// }
	BindingRuleBindTypeService = "service"

	// BindingRuleBindTypeRole is the binding rule bind type that only allows
	// the binding rule to function if a role with the given name (BindName)
	// exists at login-time. If it does the token that is created is directly
	// linked to that role like:
	//
	// &ACLToken{
	//   ...other fields...
	//   Roles: []ACLTokenRoleLink{
	//     { Name: "<computed BindName>" }
	//   }
	// }
	//
	// If it does not exist at login-time the rule is ignored.
	BindingRuleBindTypeRole = "role"

	// BindingRuleBindTypeNode is the binding rule bind type that assigns
	// a Node Identity to the token that is created using the value of
	// the computed BindName as the NodeName like:
	//
	// &ACLToken{
	//   ...other fields...
	//   NodeIdentities: []*ACLNodeIdentity{
	//     &ACLNodeIdentity{
	//       NodeName: "<computed BindName>",
	//       Datacenter: "<local datacenter of the binding rule>"
	//     }
	//   }
	// }
	BindingRuleBindTypeNode = "node"
)

type ACLBindingRule struct {
	// ID is the internal UUID associated with the binding rule
	ID string

	// Description is a human readable description (Optional)
	Description string

	// AuthMethod is the name of the auth method for which this rule applies.
	AuthMethod string

	// Selector is an expression that matches against verified identity
	// attributes returned from the auth method during login.
	Selector string

	// BindType adjusts how this binding rule is applied at login time.  The
	// valid values are:
	//
	//  - BindingRuleBindTypeService = "service"
	//  - BindingRuleBindTypeRole    = "role"
	BindType string

	// BindName is the target of the binding. Can be lightly templated using
	// HIL ${foo} syntax from available field names. How it is used depends
	// upon the BindType.
	BindName string

	// Embedded Enterprise ACL metadata
	EnterpriseMeta `mapstructure:",squash"`

	// Embedded Raft Metadata
	RaftIndex `hash:"ignore"`
}

func (r *ACLBindingRule) Clone() *ACLBindingRule {
	r2 := *r
	return &r2
}

type ACLBindingRules []*ACLBindingRule

func (rules ACLBindingRules) Sort() {
	sort.Slice(rules, func(i, j int) bool {
		return rules[i].ID < rules[j].ID
	})
}

type ACLAuthMethodListStub struct {
	Name        string
	Type        string
	DisplayName string `json:",omitempty"`
	Description string `json:",omitempty"`
	CreateIndex uint64
	ModifyIndex uint64
	EnterpriseMeta
}

func (p *ACLAuthMethod) Stub() *ACLAuthMethodListStub {
	return &ACLAuthMethodListStub{
		Name:           p.Name,
		Type:           p.Type,
		DisplayName:    p.DisplayName,
		Description:    p.Description,
		CreateIndex:    p.CreateIndex,
		ModifyIndex:    p.ModifyIndex,
		EnterpriseMeta: p.EnterpriseMeta,
	}
}

type ACLAuthMethods []*ACLAuthMethod
type ACLAuthMethodListStubs []*ACLAuthMethodListStub

func (methods ACLAuthMethods) Sort() {
	sort.Slice(methods, func(i, j int) bool {
		return methods[i].Name < methods[j].Name
	})
}

func (methods ACLAuthMethodListStubs) Sort() {
	sort.Slice(methods, func(i, j int) bool {
		return methods[i].Name < methods[j].Name
	})
}

type ACLAuthMethod struct {
	// Name is a unique identifier for this specific auth method.
	//
	// Immutable once set and only settable during create.
	Name string

	// Type is the type of the auth method this is.
	//
	// Immutable once set and only settable during create.
	Type string

	// DisplayName is an optional name to use instead of the Name field when
	// displaying information about this auth method in any kind of user
	// interface.
	DisplayName string `json:",omitempty"`

	// Description is just an optional bunch of explanatory text.
	Description string `json:",omitempty"`

	// MaxTokenTTL this is the maximum life of a token created by this method.
	MaxTokenTTL time.Duration `json:",omitempty"`

	// TokenLocality defines the kind of token that this auth method produces.
	// This can be either 'local' or 'global'. If empty 'local' is assumed.
	TokenLocality string `json:",omitempty"`

	// Configuration is arbitrary configuration for the auth method. This
	// should only contain primitive values and containers (such as lists and
	// maps).
	Config map[string]interface{}

	// Embedded Enterprise ACL Meta
	EnterpriseMeta `mapstructure:",squash"`

	ACLAuthMethodEnterpriseFields `mapstructure:",squash"`

	// Embedded Raft Metadata
	RaftIndex `hash:"ignore"`
}

func (m *ACLAuthMethod) MarshalJSON() ([]byte, error) {
	type Alias ACLAuthMethod
	exported := &struct {
		MaxTokenTTL string `json:",omitempty"`
		*Alias
	}{
		MaxTokenTTL: m.MaxTokenTTL.String(),
		Alias:       (*Alias)(m),
	}
	if m.MaxTokenTTL == 0 {
		exported.MaxTokenTTL = ""
	}

	return json.Marshal(exported)
}

func (m *ACLAuthMethod) UnmarshalJSON(data []byte) (err error) {
	type Alias ACLAuthMethod
	aux := &struct {
		MaxTokenTTL interface{}
		*Alias
	}{
		Alias: (*Alias)(m),
	}
	if err = lib.UnmarshalJSON(data, &aux); err != nil {
		return err
	}
	if aux.MaxTokenTTL != nil {
		switch v := aux.MaxTokenTTL.(type) {
		case string:
			if m.MaxTokenTTL, err = time.ParseDuration(v); err != nil {
				return err
			}
		case float64:
			m.MaxTokenTTL = time.Duration(v)
		}
	}

	return nil
}

type ACLReplicationType string

const (
	ACLReplicateLegacy   ACLReplicationType = "legacy"
	ACLReplicatePolicies ACLReplicationType = "policies"
	ACLReplicateRoles    ACLReplicationType = "roles"
	ACLReplicateTokens   ACLReplicationType = "tokens"
)

func (t ACLReplicationType) SingularNoun() string {
	switch t {
	case ACLReplicateLegacy:
		return "legacy"
	case ACLReplicatePolicies:
		return "policy"
	case ACLReplicateRoles:
		return "role"
	case ACLReplicateTokens:
		return "token"
	default:
		return "<UNKNOWN>"
	}
}

// ACLReplicationStatus provides information about the health of the ACL
// replication system.
type ACLReplicationStatus struct {
	Enabled              bool
	Running              bool
	SourceDatacenter     string
	ReplicationType      ACLReplicationType
	ReplicatedIndex      uint64
	ReplicatedRoleIndex  uint64
	ReplicatedTokenIndex uint64
	LastSuccess          time.Time
	LastError            time.Time
}

// ACLTokenSetRequest is used for token creation and update operations
// at the RPC layer
type ACLTokenSetRequest struct {
	ACLToken   ACLToken // Token to manipulate - I really dislike this name but "Token" is taken in the WriteRequest
	Create     bool     // Used to explicitly mark this request as a creation
	Datacenter string   // The datacenter to perform the request within
	WriteRequest
}

func (r *ACLTokenSetRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLTokenGetRequest is used for token read operations at the RPC layer
type ACLTokenGetRequest struct {
	TokenID     string         // id used for the token lookup
	TokenIDType ACLTokenIDType // The Type of ID used to lookup the token
	Datacenter  string         // The datacenter to perform the request within
	EnterpriseMeta
	QueryOptions
}

func (r *ACLTokenGetRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLTokenDeleteRequest is used for token deletion operations at the RPC layer
type ACLTokenDeleteRequest struct {
	TokenID    string // ID of the token to delete
	Datacenter string // The datacenter to perform the request within
	EnterpriseMeta
	WriteRequest
}

func (r *ACLTokenDeleteRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLTokenListRequest is used for token listing operations at the RPC layer
type ACLTokenListRequest struct {
	IncludeLocal  bool   // Whether local tokens should be included
	IncludeGlobal bool   // Whether global tokens should be included
	Policy        string // Policy filter
	Role          string // Role filter
	AuthMethod    string // Auth Method filter
	Datacenter    string // The datacenter to perform the request within
	ACLAuthMethodEnterpriseMeta
	EnterpriseMeta
	QueryOptions
}

func (r *ACLTokenListRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLTokenListResponse is used to return the secret data free stubs
// of the tokens
type ACLTokenListResponse struct {
	Tokens ACLTokenListStubs
	QueryMeta
}

// ACLTokenBatchGetRequest is used for reading multiple tokens, this is
// different from the the token list request in that only tokens with the
// the requested ids are returned
type ACLTokenBatchGetRequest struct {
	AccessorIDs []string // List of accessor ids to fetch
	Datacenter  string   // The datacenter to perform the request within
	QueryOptions
}

func (r *ACLTokenBatchGetRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLTokenBatchSetRequest is used only at the Raft layer
// for batching multiple token creation/update operations
//
// This is particularly useful during token replication and during
// automatic legacy token upgrades.
type ACLTokenBatchSetRequest struct {
	Tokens               ACLTokens
	CAS                  bool
	AllowMissingLinks    bool
	ProhibitUnprivileged bool
	FromReplication      bool
}

// ACLTokenBatchDeleteRequest is used only at the Raft layer
// for batching multiple token deletions.
//
// This is particularly useful during token replication when
// multiple tokens need to be removed from the local DCs state.
type ACLTokenBatchDeleteRequest struct {
	TokenIDs []string // Tokens to delete
}

// ACLTokenBootstrapRequest is used only at the Raft layer
// for ACL bootstrapping
//
// The RPC layer will use a generic DCSpecificRequest to indicate
// that bootstrapping must be performed but the actual token
// and the resetIndex will be generated by that RPC endpoint
type ACLTokenBootstrapRequest struct {
	Token      ACLToken // Token to use for bootstrapping
	ResetIndex uint64   // Reset index
}

// ACLTokenResponse returns a single Token + metadata
type ACLTokenResponse struct {
	Token            *ACLToken
	Redacted         bool // whether the token's secret was redacted
	SourceDatacenter string
	QueryMeta
}

// ACLTokenBatchResponse returns multiple Tokens associated with the same metadata
type ACLTokenBatchResponse struct {
	Tokens   []*ACLToken
	Redacted bool // whether the token secrets were redacted.
	Removed  bool // whether any tokens were completely removed
	QueryMeta
}

// ACLPolicySetRequest is used at the RPC layer for creation and update requests
type ACLPolicySetRequest struct {
	Policy     ACLPolicy // The policy to upsert
	Datacenter string    // The datacenter to perform the request within
	WriteRequest
}

func (r *ACLPolicySetRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLPolicyDeleteRequest is used at the RPC layer deletion requests
type ACLPolicyDeleteRequest struct {
	PolicyID   string // The id of the policy to delete
	Datacenter string // The datacenter to perform the request within
	EnterpriseMeta
	WriteRequest
}

func (r *ACLPolicyDeleteRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLPolicyGetRequest is used at the RPC layer to perform policy read operations
type ACLPolicyGetRequest struct {
	PolicyID   string // id used for the policy lookup (one of PolicyID or PolicyName is allowed)
	PolicyName string // name used for the policy lookup (one of PolicyID or PolicyName is allowed)
	Datacenter string // The datacenter to perform the request within
	EnterpriseMeta
	QueryOptions
}

func (r *ACLPolicyGetRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLPolicyListRequest is used at the RPC layer to request a listing of policies
type ACLPolicyListRequest struct {
	Datacenter string // The datacenter to perform the request within
	EnterpriseMeta
	QueryOptions
}

func (r *ACLPolicyListRequest) RequestDatacenter() string {
	return r.Datacenter
}

type ACLPolicyListResponse struct {
	Policies ACLPolicyListStubs
	QueryMeta
}

// ACLPolicyBatchGetRequest is used at the RPC layer to request a subset of
// the policies associated with the token used for retrieval
type ACLPolicyBatchGetRequest struct {
	PolicyIDs  []string // List of policy ids to fetch
	Datacenter string   // The datacenter to perform the request within
	QueryOptions
}

func (r *ACLPolicyBatchGetRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLPolicyResponse returns a single policy + metadata
type ACLPolicyResponse struct {
	Policy *ACLPolicy
	QueryMeta
}

type ACLPolicyBatchResponse struct {
	Policies []*ACLPolicy
	QueryMeta
}

// ACLPolicyBatchSetRequest is used at the Raft layer for batching
// multiple policy creations and updates
//
// This is particularly useful during replication
type ACLPolicyBatchSetRequest struct {
	Policies ACLPolicies
}

// ACLPolicyBatchDeleteRequest is used at the Raft layer for batching
// multiple policy deletions
//
// This is particularly useful during replication
type ACLPolicyBatchDeleteRequest struct {
	PolicyIDs []string
}

func CloneStringSlice(s []string) []string {
	if len(s) == 0 {
		return nil
	}
	out := make([]string, len(s))
	copy(out, s)
	return out
}

// ACLRoleSetRequest is used at the RPC layer for creation and update requests
type ACLRoleSetRequest struct {
	Role       ACLRole // The role to upsert
	Datacenter string  // The datacenter to perform the request within
	WriteRequest
}

func (r *ACLRoleSetRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLRoleDeleteRequest is used at the RPC layer deletion requests
type ACLRoleDeleteRequest struct {
	RoleID     string // id of the role to delete
	Datacenter string // The datacenter to perform the request within
	EnterpriseMeta
	WriteRequest
}

func (r *ACLRoleDeleteRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLRoleGetRequest is used at the RPC layer to perform role read operations
type ACLRoleGetRequest struct {
	RoleID     string // id used for the role lookup (one of RoleID or RoleName is allowed)
	RoleName   string // name used for the role lookup (one of RoleID or RoleName is allowed)
	Datacenter string // The datacenter to perform the request within
	EnterpriseMeta
	QueryOptions
}

func (r *ACLRoleGetRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLRoleListRequest is used at the RPC layer to request a listing of roles
type ACLRoleListRequest struct {
	Policy     string // Policy filter
	Datacenter string // The datacenter to perform the request within
	EnterpriseMeta
	QueryOptions
}

func (r *ACLRoleListRequest) RequestDatacenter() string {
	return r.Datacenter
}

type ACLRoleListResponse struct {
	Roles ACLRoles
	QueryMeta
}

// ACLRoleBatchGetRequest is used at the RPC layer to request a subset of
// the roles associated with the token used for retrieval
type ACLRoleBatchGetRequest struct {
	RoleIDs    []string // List of role ids to fetch
	Datacenter string   // The datacenter to perform the request within
	QueryOptions
}

func (r *ACLRoleBatchGetRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLRoleResponse returns a single role + metadata
type ACLRoleResponse struct {
	Role *ACLRole
	QueryMeta
}

type ACLRoleBatchResponse struct {
	Roles []*ACLRole
	QueryMeta
}

// ACLRoleBatchSetRequest is used at the Raft layer for batching
// multiple role creations and updates
//
// This is particularly useful during replication
type ACLRoleBatchSetRequest struct {
	Roles             ACLRoles
	AllowMissingLinks bool
}

// ACLRoleBatchDeleteRequest is used at the Raft layer for batching
// multiple role deletions
//
// This is particularly useful during replication
type ACLRoleBatchDeleteRequest struct {
	RoleIDs []string
}

// ACLBindingRuleSetRequest is used at the RPC layer for creation and update requests
type ACLBindingRuleSetRequest struct {
	BindingRule ACLBindingRule // The rule to upsert
	Datacenter  string         // The datacenter to perform the request within
	WriteRequest
}

func (r *ACLBindingRuleSetRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLBindingRuleDeleteRequest is used at the RPC layer deletion requests
type ACLBindingRuleDeleteRequest struct {
	BindingRuleID string // id of the rule to delete
	Datacenter    string // The datacenter to perform the request within
	EnterpriseMeta
	WriteRequest
}

func (r *ACLBindingRuleDeleteRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLBindingRuleGetRequest is used at the RPC layer to perform rule read operations
type ACLBindingRuleGetRequest struct {
	BindingRuleID string // id used for the rule lookup
	Datacenter    string // The datacenter to perform the request within
	EnterpriseMeta
	QueryOptions
}

func (r *ACLBindingRuleGetRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLBindingRuleListRequest is used at the RPC layer to request a listing of rules
type ACLBindingRuleListRequest struct {
	AuthMethod string // optional filter
	Datacenter string // The datacenter to perform the request within
	EnterpriseMeta
	QueryOptions
}

func (r *ACLBindingRuleListRequest) RequestDatacenter() string {
	return r.Datacenter
}

type ACLBindingRuleListResponse struct {
	BindingRules ACLBindingRules
	QueryMeta
}

// ACLBindingRuleResponse returns a single binding + metadata
type ACLBindingRuleResponse struct {
	BindingRule *ACLBindingRule
	QueryMeta
}

// ACLBindingRuleBatchSetRequest is used at the Raft layer for batching
// multiple rule creations and updates
type ACLBindingRuleBatchSetRequest struct {
	BindingRules ACLBindingRules
}

// ACLBindingRuleBatchDeleteRequest is used at the Raft layer for batching
// multiple rule deletions
type ACLBindingRuleBatchDeleteRequest struct {
	BindingRuleIDs []string
}

// ACLAuthMethodSetRequest is used at the RPC layer for creation and update requests
type ACLAuthMethodSetRequest struct {
	AuthMethod ACLAuthMethod // The auth method to upsert
	Datacenter string        // The datacenter to perform the request within
	WriteRequest
}

func (r *ACLAuthMethodSetRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLAuthMethodDeleteRequest is used at the RPC layer deletion requests
type ACLAuthMethodDeleteRequest struct {
	AuthMethodName string // name of the auth method to delete
	Datacenter     string // The datacenter to perform the request within
	EnterpriseMeta
	WriteRequest
}

func (r *ACLAuthMethodDeleteRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLAuthMethodGetRequest is used at the RPC layer to perform rule read operations
type ACLAuthMethodGetRequest struct {
	AuthMethodName string // name used for the auth method lookup
	Datacenter     string // The datacenter to perform the request within
	EnterpriseMeta
	QueryOptions
}

func (r *ACLAuthMethodGetRequest) RequestDatacenter() string {
	return r.Datacenter
}

// ACLAuthMethodListRequest is used at the RPC layer to request a listing of auth methods
type ACLAuthMethodListRequest struct {
	Datacenter string // The datacenter to perform the request within
	EnterpriseMeta
	QueryOptions
}

func (r *ACLAuthMethodListRequest) RequestDatacenter() string {
	return r.Datacenter
}

type ACLAuthMethodListResponse struct {
	AuthMethods ACLAuthMethodListStubs
	QueryMeta
}

// ACLAuthMethodResponse returns a single auth method + metadata
type ACLAuthMethodResponse struct {
	AuthMethod *ACLAuthMethod
	QueryMeta
}

// ACLAuthMethodBatchSetRequest is used at the Raft layer for batching
// multiple auth method creations and updates
type ACLAuthMethodBatchSetRequest struct {
	AuthMethods ACLAuthMethods
}

// ACLAuthMethodBatchDeleteRequest is used at the Raft layer for batching
// multiple auth method deletions
type ACLAuthMethodBatchDeleteRequest struct {
	AuthMethodNames []string
	// While it may seem odd that AuthMethodNames is associated with a single
	// EnterpriseMeta, it is okay as this struct is only ever used to
	// delete a single entry. This is because AuthMethods unlike tokens, policies
	// and roles are not replicated between datacenters and therefore never
	// batch applied.
	EnterpriseMeta
}

type ACLLoginParams struct {
	AuthMethod  string
	BearerToken string
	Meta        map[string]string `json:",omitempty"`
	EnterpriseMeta
}

type ACLLoginRequest struct {
	Auth       *ACLLoginParams
	Datacenter string // The datacenter to perform the request within
	WriteRequest
}

func (r *ACLLoginRequest) RequestDatacenter() string {
	return r.Datacenter
}

type ACLLogoutRequest struct {
	Datacenter string // The datacenter to perform the request within
	WriteRequest
}

func (r *ACLLogoutRequest) RequestDatacenter() string {
	return r.Datacenter
}

type RemoteACLAuthorizationRequest struct {
	Datacenter string
	Requests   []ACLAuthorizationRequest
	QueryOptions
}

type ACLAuthorizationRequest struct {
	Resource acl.Resource
	Segment  string `json:",omitempty"`
	Access   string
	EnterpriseMeta
}

type ACLAuthorizationResponse struct {
	ACLAuthorizationRequest
	Allow bool
}

func (r *RemoteACLAuthorizationRequest) RequestDatacenter() string {
	return r.Datacenter
}

func CreateACLAuthorizationResponses(authz acl.Authorizer, requests []ACLAuthorizationRequest) ([]ACLAuthorizationResponse, error) {
	responses := make([]ACLAuthorizationResponse, len(requests))
	var ctx acl.AuthorizerContext

	for idx, req := range requests {
		req.FillAuthzContext(&ctx)
		decision, err := acl.Enforce(authz, req.Resource, req.Segment, req.Access, &ctx)
		if err != nil {
			return nil, err
		}

		responses[idx].ACLAuthorizationRequest = req
		responses[idx].Allow = decision == acl.Allow
	}

	return responses, nil
}
