// Copyright IBM Corp. 2016, 2025
// SPDX-License-Identifier: BUSL-1.1

package vault

import (
	"context"
	"fmt"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/armon/go-metrics"
	log "github.com/hashicorp/go-hclog"
	"github.com/hashicorp/go-secure-stdlib/strutil"
	lru "github.com/hashicorp/golang-lru"
	"github.com/hashicorp/vault/helper/identity"
	"github.com/hashicorp/vault/helper/namespace"
	"github.com/hashicorp/vault/sdk/helper/consts"
	"github.com/hashicorp/vault/sdk/logical"
	"github.com/hashicorp/vault/vault/observations"
)

const (
	// policySubPath is the sub-path used for the policy store view. This is
	// nested under the system view. policyRGPSubPath/policyEGPSubPath are
	// similar but for RGPs/EGPs.
	policyACLSubPath = "policy/"
	policyRGPSubPath = "policy-rgp/"
	policyEGPSubPath = "policy-egp/"

	// policyCacheSize is the number of policies that are kept cached
	policyCacheSize = 1024

	// defaultPolicyName is the name of the default policy
	defaultPolicyName = "default"

	// responseWrappingPolicyName is the name of the fixed policy
	responseWrappingPolicyName = "response-wrapping"

	// controlGroupPolicyName is the name of the fixed policy for control group
	// tokens
	controlGroupPolicyName = "control-group"

	// responseWrappingPolicy is the policy that ensures cubbyhole response
	// wrapping can always succeed.
	responseWrappingPolicy = `
path "cubbyhole/response" {
    capabilities = ["create", "read"]
}

path "sys/wrapping/unwrap" {
    capabilities = ["update"]
}
`
	// controlGroupPolicy is the policy that ensures control group requests can
	// commit themselves
	controlGroupPolicy = `
path "cubbyhole/control-group" {
    capabilities = ["update", "create", "read"]
}

path "sys/wrapping/unwrap" {
    capabilities = ["update"]
}
`
	// defaultPolicy is the "default" policy
	defaultPolicy = `
# Allow tokens to look up their own properties
path "auth/token/lookup-self" {
    capabilities = ["read"]
}

# Allow tokens to renew themselves
path "auth/token/renew-self" {
    capabilities = ["update"]
}

# Allow tokens to revoke themselves
path "auth/token/revoke-self" {
    capabilities = ["update"]
}

# Allow a token to look up its own capabilities on a path
path "sys/capabilities-self" {
    capabilities = ["update"]
}

# Allow a token to look up its own entity by id or name
path "identity/entity/id/{{identity.entity.id}}" {
  capabilities = ["read"]
}
path "identity/entity/name/{{identity.entity.name}}" {
  capabilities = ["read"]
}


# Allow a token to look up its resultant ACL from all policies. This is useful
# for UIs. It is an internal path because the format may change at any time
# based on how the internal ACL features and capabilities change.
path "sys/internal/ui/resultant-acl" {
    capabilities = ["read"]
}

# Allow a token to renew a lease via lease_id in the request body; old path for
# old clients, new path for newer
path "sys/renew" {
    capabilities = ["update"]
}
path "sys/leases/renew" {
    capabilities = ["update"]
}

# Allow looking up lease properties. This requires knowing the lease ID ahead
# of time and does not divulge any sensitive information.
path "sys/leases/lookup" {
    capabilities = ["update"]
}

# Allow a token to manage its own cubbyhole
path "cubbyhole/*" {
    capabilities = ["create", "read", "update", "delete", "list"]
}

# Allow a token to wrap arbitrary values in a response-wrapping token
path "sys/wrapping/wrap" {
    capabilities = ["update"]
}

# Allow a token to look up the creation time and TTL of a given
# response-wrapping token
path "sys/wrapping/lookup" {
    capabilities = ["update"]
}

# Allow a token to unwrap a response-wrapping token. This is a convenience to
# avoid client token swapping since this is also part of the response wrapping
# policy.
path "sys/wrapping/unwrap" {
    capabilities = ["update"]
}

# Allow general purpose tools
path "sys/tools/hash" {
    capabilities = ["update"]
}
path "sys/tools/hash/*" {
    capabilities = ["update"]
}

# Allow checking the status of a Control Group request if the user has the
# accessor
path "sys/control-group/request" {
    capabilities = ["update"]
}

# Allow a token to make requests to the Authorization Endpoint for OIDC providers.
path "identity/oidc/provider/+/authorize" {
    capabilities = ["read", "update"]
}
`
)

var (
	immutablePolicies = []string{
		"root",
		responseWrappingPolicyName,
		controlGroupPolicyName,
	}
	nonAssignablePolicies = []string{
		responseWrappingPolicyName,
		controlGroupPolicyName,
	}
)

// PolicyStore is used to provide durable storage of policy, and to
// manage ACLs associated with them.
type PolicyStore struct {
	entPolicyStore

	core    *Core
	aclView *BarrierView
	rgpView *BarrierView
	egpView *BarrierView

	tokenPoliciesLRU *lru.TwoQueueCache
	egpLRU           *lru.TwoQueueCache

	// This is used to ensure that writes to the store (acl/rgp) or to the egp
	// path tree don't happen concurrently. We are okay reading stale data so
	// long as there aren't concurrent writes.
	modifyLock *sync.RWMutex

	// Stores whether a token policy is ACL or RGP
	policyTypeMap sync.Map

	// logger is the server logger copied over from core
	logger log.Logger

	// deniedParamWarn tracks if we've already logged about the system using allowed_parameters or denied_parameters
	deniedParamWarnOnce sync.Once
}

// PolicyEntry is used to store a policy by name
type PolicyEntry struct {
	sentinelPolicy

	Version   int
	Raw       string
	Templated bool
	Type      PolicyType
}

// NewPolicyStore creates a new PolicyStore that is backed
// using a given view. It used used to durable store and manage named policy.
func NewPolicyStore(ctx context.Context, core *Core, baseView *BarrierView, system logical.SystemView, logger log.Logger) (*PolicyStore, error) {
	ps := &PolicyStore{
		aclView:    baseView.SubView(policyACLSubPath),
		rgpView:    baseView.SubView(policyRGPSubPath),
		egpView:    baseView.SubView(policyEGPSubPath),
		modifyLock: new(sync.RWMutex),
		logger:     logger,
		core:       core,
	}

	ps.extraInit()

	if !system.CachingDisabled() {
		cache, _ := lru.New2Q(policyCacheSize)
		ps.tokenPoliciesLRU = cache
		cache, _ = lru.New2Q(policyCacheSize)
		ps.egpLRU = cache
	}

	aclView := ps.getACLView(namespace.RootNamespace)
	keys, err := logical.CollectKeys(namespace.RootContext(ctx), aclView)
	if err != nil {
		ps.logger.Error("error collecting acl policy keys", "error", err)
		return nil, err
	}
	for _, key := range keys {
		index := ps.cacheKey(namespace.RootNamespace, ps.sanitizeName(key))
		ps.policyTypeMap.Store(index, PolicyTypeACL)
	}

	if err := ps.loadNamespacePolicies(ctx, core); err != nil {
		return nil, err
	}

	// Special-case root; doesn't exist on disk but does need to be found
	ps.policyTypeMap.Store(ps.cacheKey(namespace.RootNamespace, "root"), PolicyTypeACL)
	return ps, nil
}

// setupPolicyStore is used to initialize the policy store
// when the vault is being unsealed.
func (c *Core) setupPolicyStore(ctx context.Context) error {
	// Create the policy store
	var err error
	sysView := &dynamicSystemView{core: c, perfStandby: c.perfStandby}
	psLogger := c.baseLogger.Named("policy")
	c.AddLogger(psLogger)
	c.policyStore, err = NewPolicyStore(ctx, c, c.systemBarrierView, sysView, psLogger)
	if err != nil {
		return err
	}

	if c.ReplicationState().HasState(consts.ReplicationPerformanceSecondary | consts.ReplicationDRSecondary) {
		// Policies will sync from the primary
		return nil
	}

	if c.perfStandby {
		// Policies will sync from the active
		return nil
	}

	// Ensure that the default policy exists, and if not, create it
	if err := c.policyStore.loadACLPolicy(ctx, defaultPolicyName, defaultPolicy); err != nil {
		return err
	}
	// Ensure that the response wrapping policy exists
	if err := c.policyStore.loadACLPolicy(ctx, responseWrappingPolicyName, responseWrappingPolicy); err != nil {
		return err
	}
	// Ensure that the control group policy exists
	if err := c.policyStore.loadACLPolicy(ctx, controlGroupPolicyName, controlGroupPolicy); err != nil {
		return err
	}

	return nil
}

// teardownPolicyStore is used to reverse setupPolicyStore
// when the vault is being sealed.
func (c *Core) teardownPolicyStore() error {
	c.policyStore = nil
	return nil
}

func (ps *PolicyStore) invalidate(ctx context.Context, name string, policyType PolicyType) {
	ns, err := namespace.FromContext(ctx)
	if err != nil {
		ps.logger.Error("unable to invalidate key, no namespace info passed", "key", name)
		return
	}

	// This may come with a prefixed "/" due to joining the file path
	saneName := strings.TrimPrefix(name, "/")
	index := ps.cacheKey(ns, saneName)

	ps.modifyLock.Lock()
	defer ps.modifyLock.Unlock()

	// We don't lock before removing from the LRU here because the worst that
	// can happen is we load again if something since added it
	switch policyType {
	case PolicyTypeACL, PolicyTypeRGP:
		if ps.tokenPoliciesLRU != nil {
			ps.tokenPoliciesLRU.Remove(index)
		}

	case PolicyTypeEGP:
		if ps.egpLRU != nil {
			ps.egpLRU.Remove(index)
		}

	default:
		// Can't do anything
		return
	}

	// Force a reload
	out, err := ps.switchedGetPolicy(ctx, name, policyType, false)
	if err != nil {
		ps.logger.Error("error fetching policy after invalidation", "name", saneName)
	}

	// If true, the invalidation was actually a delete, so we may need to
	// perform further deletion tasks. We skip the physical deletion just in
	// case another process has re-written the policy; instead next time Get is
	// called the values will be loaded back in.
	if out == nil {
		ps.switchedDeletePolicy(ctx, name, policyType, false, true, nil)
	}

	return
}

// SetPolicyWithRequest is used to create or update a given policy from a request
func (ps *PolicyStore) SetPolicyWithRequest(ctx context.Context, p *Policy, req *logical.Request) error {
	defer metrics.MeasureSince([]string{"policy", "set_policy"}, time.Now())
	if p == nil {
		return fmt.Errorf("nil policy passed in for storage")
	}
	if p.Name == "" {
		return fmt.Errorf("policy name missing")
	}
	// Policies are normalized to lower-case
	p.Name = ps.sanitizeName(p.Name)
	if strutil.StrListContains(immutablePolicies, p.Name) {
		return fmt.Errorf("cannot update %q policy", p.Name)
	}

	return ps.setPolicyInternal(ctx, p, req)
}

// SetPolicy is used to create or update the given policy
func (ps *PolicyStore) SetPolicy(ctx context.Context, p *Policy) error {
	return ps.SetPolicyWithRequest(ctx, p, nil)
}

func (ps *PolicyStore) setPolicyInternal(ctx context.Context, p *Policy, req *logical.Request) error {
	ps.modifyLock.Lock()
	defer ps.modifyLock.Unlock()

	// Get the appropriate view based on policy type and namespace
	view := ps.getBarrierView(p.namespace, p.Type)
	if view == nil {
		return fmt.Errorf("unable to get the barrier subview for policy type %q", p.Type)
	}

	if err := ps.parseEGPPaths(p); err != nil {
		return err
	}

	// Create the entry
	entry, err := logical.StorageEntryJSON(p.Name, &PolicyEntry{
		Version:        2,
		Raw:            p.Raw,
		Type:           p.Type,
		Templated:      p.Templated,
		sentinelPolicy: p.sentinelPolicy,
	})
	if err != nil {
		return fmt.Errorf("failed to create entry: %w", err)
	}

	// Construct the cache key
	index := ps.cacheKey(p.namespace, p.Name)

	switch p.Type {
	case PolicyTypeACL:
		rgpView := ps.getRGPView(p.namespace)
		rgp, err := rgpView.Get(ctx, entry.Key)
		if err != nil {
			return fmt.Errorf("failed looking up conflicting policy: %w", err)
		}
		if rgp != nil {
			return fmt.Errorf("cannot reuse policy names between ACLs and RGPs")
		}

		if err := view.Put(ctx, entry); err != nil {
			return fmt.Errorf("failed to persist policy: %w", err)
		}

		ps.policyTypeMap.Store(index, PolicyTypeACL)

		if ps.tokenPoliciesLRU != nil {
			ps.tokenPoliciesLRU.Add(index, p)
		}

		ps.logDeniedParamWarning(p)

	case PolicyTypeRGP:
		aclView := ps.getACLView(p.namespace)
		acl, err := aclView.Get(ctx, entry.Key)
		if err != nil {
			return fmt.Errorf("failed looking up conflicting policy: %w", err)
		}
		if acl != nil {
			return fmt.Errorf("cannot reuse policy names between ACLs and RGPs")
		}

		if err := ps.handleSentinelPolicy(ctx, p, view, entry); err != nil {
			return err
		}

		ps.policyTypeMap.Store(index, PolicyTypeRGP)

		// We load here after successfully loading into Sentinel so that on
		// error we will try loading again on the next get
		if ps.tokenPoliciesLRU != nil {
			ps.tokenPoliciesLRU.Add(index, p)
		}

	case PolicyTypeEGP:
		if err := ps.handleSentinelPolicy(ctx, p, view, entry); err != nil {
			return err
		}

		// We load here after successfully loading into Sentinel so that on
		// error we will try loading again on the next get
		if ps.egpLRU != nil {
			ps.egpLRU.Add(index, p)
		}

	default:
		return fmt.Errorf("unknown policy type, cannot set")
	}

	var clientId string
	var entityId string
	var requestId string
	if req != nil {
		clientId = req.ClientID
		entityId = req.EntityID
		requestId = req.ID
	}
	err = ps.core.Observations().RecordObservationToLedger(ctx, observations.ObservationTypePolicyUpsert, p.namespace, map[string]interface{}{
		"client_id":  clientId,
		"entity_id":  entityId,
		"request_id": requestId,
		"type":       p.Type.String(),
		"name":       p.Name,
		"path_rules": pathRulesToObservationPathRules(p.Paths),
	})
	if err != nil {
		ps.logger.Error("error recording observation for policy upsert", err)
	}

	return nil
}

// ObservationPathRules is a minimal version of PathRules to contain only
// salient information for observations.
type ObservationPathRules struct {
	Path                string   `json:"path"`
	Capabilities        []string `json:"capabilities"`
	Prefix              bool     `json:"prefix"`
	HasSegmentWildcards bool     `json:"has_segment_wildcards"`
}

// pathRulesToObservationPathRules translated a list of PathRules into a list of ObservationPathRules
func pathRulesToObservationPathRules(rules []*PathRules) []*ObservationPathRules {
	observationPathRules := make([]*ObservationPathRules, 0, len(rules))
	for _, rule := range rules {
		if rule != nil {
			observationPathRules = append(observationPathRules, &ObservationPathRules{
				Path:                rule.Path,
				Capabilities:        rule.Capabilities,
				Prefix:              rule.IsPrefix,
				HasSegmentWildcards: rule.HasSegmentWildcards,
			})
		}
	}
	return observationPathRules
}

// GetNonEGPPolicyType returns a policy's type.
// It will return an error if the policy doesn't exist in the store or isn't
// an ACL or a Sentinel Role Governing Policy (RGP).
//
// Note: Sentinel Endpoint Governing Policies (EGPs) are not stored within the
// policyTypeMap. We sometimes need to distinguish between ACLs and RGPs due to
// them both being token policies, but the logic related to EGPs is separate
// enough that it is never necessary to look up their type.
func (ps *PolicyStore) GetNonEGPPolicyType(nsID string, name string) (*PolicyType, error) {
	sanitizedName := ps.sanitizeName(name)
	index := path.Join(nsID, sanitizedName)

	pt, ok := ps.policyTypeMap.Load(index)
	if !ok {
		// Doesn't exist
		return nil, ErrPolicyNotExistInTypeMap
	}

	policyType, ok := pt.(PolicyType)
	if !ok {
		return nil, fmt.Errorf("unknown policy type for: %v", index)
	}

	return &policyType, nil
}

// GetPolicy is used to fetch the named policy
func (ps *PolicyStore) GetPolicy(ctx context.Context, name string, policyType PolicyType) (*Policy, error) {
	return ps.switchedGetPolicy(ctx, name, policyType, true)
}

func (ps *PolicyStore) switchedGetPolicy(ctx context.Context, name string, policyType PolicyType, grabLock bool) (*Policy, error) {
	defer metrics.MeasureSince([]string{"policy", "get_policy"}, time.Now())

	ns, err := namespace.FromContext(ctx)
	if err != nil {
		return nil, err
	}
	// Policies are normalized to lower-case
	name = ps.sanitizeName(name)
	index := ps.cacheKey(ns, name)

	var cache *lru.TwoQueueCache
	var view *BarrierView

	switch policyType {
	case PolicyTypeACL:
		cache = ps.tokenPoliciesLRU
		view = ps.getACLView(ns)
	case PolicyTypeRGP:
		cache = ps.tokenPoliciesLRU
		view = ps.getRGPView(ns)
	case PolicyTypeEGP:
		cache = ps.egpLRU
		view = ps.getEGPView(ns)
	case PolicyTypeToken:
		cache = ps.tokenPoliciesLRU
		val, ok := ps.policyTypeMap.Load(index)
		if !ok {
			// Doesn't exist
			return nil, nil
		}
		policyType = val.(PolicyType)
		switch policyType {
		case PolicyTypeACL:
			view = ps.getACLView(ns)
		case PolicyTypeRGP:
			view = ps.getRGPView(ns)
		default:
			return nil, fmt.Errorf("invalid type of policy in type map: %q", policyType)
		}
	}

	if cache != nil {
		// Check for cached policy
		if raw, ok := cache.Get(index); ok {
			return raw.(*Policy), nil
		}
	}

	// Special case the root policy
	if policyType == PolicyTypeACL && name == "root" && ns.ID == namespace.RootNamespaceID {
		p := &Policy{
			Name:      "root",
			namespace: namespace.RootNamespace,
		}
		if cache != nil {
			cache.Add(index, p)
		}
		return p, nil
	}

	if grabLock {
		ps.modifyLock.Lock()
		defer ps.modifyLock.Unlock()
	}

	// See if anything has added it since we got the lock
	if cache != nil {
		if raw, ok := cache.Get(index); ok {
			return raw.(*Policy), nil
		}
	}

	// Nil-check on the view before proceeding to retrieve from storage
	if view == nil {
		return nil, fmt.Errorf("unable to get the barrier subview for policy type %q", policyType)
	}

	out, err := view.Get(ctx, name)
	if err != nil {
		return nil, fmt.Errorf("failed to read policy: %w", err)
	}

	if out == nil {
		return nil, nil
	}

	policyEntry := new(PolicyEntry)
	policy := new(Policy)
	err = out.DecodeJSON(policyEntry)
	if err != nil {
		return nil, fmt.Errorf("failed to parse policy: %w", err)
	}

	// Set these up here so that they're available for loading into
	// Sentinel
	policy.Name = name
	policy.Raw = policyEntry.Raw
	policy.Type = policyEntry.Type
	policy.Templated = policyEntry.Templated
	policy.sentinelPolicy = policyEntry.sentinelPolicy
	policy.namespace = ns
	switch policyEntry.Type {
	case PolicyTypeACL:
		// Parse normally
		p, duplicate, err := ParseACLPolicyCheckDuplicates(ns, policyEntry.Raw)
		if err != nil {
			return nil, fmt.Errorf("failed to parse policy: %w", err)
		}
		if duplicate {
			ps.logger.Warn("HCL policy contains duplicate attributes, which will no longer be supported in a future version", "policy", policy.Name, "namespace", policy.namespace.Path)
		}
		policy.Paths = p.Paths

		// Reset this in case they set the name in the policy itself
		policy.Name = name

		ps.policyTypeMap.Store(index, PolicyTypeACL)

	case PolicyTypeRGP:
		if err := ps.handleSentinelPolicy(ctx, policy, nil, nil); err != nil {
			return nil, err
		}

		ps.policyTypeMap.Store(index, PolicyTypeRGP)

	case PolicyTypeEGP:
		if err := ps.handleSentinelPolicy(ctx, policy, nil, nil); err != nil {
			return nil, err
		}

	default:
		return nil, fmt.Errorf("unknown policy type %q", policyEntry.Type.String())
	}

	if cache != nil {
		// Update the LRU cache
		cache.Add(index, policy)
	}

	return policy, nil
}

// ListPolicies is used to list the available policies
func (ps *PolicyStore) ListPolicies(ctx context.Context, policyType PolicyType) ([]string, error) {
	defer metrics.MeasureSince([]string{"policy", "list_policies"}, time.Now())

	ns, err := namespace.FromContext(ctx)
	if err != nil {
		return nil, err
	}
	if ns == nil {
		return nil, namespace.ErrNoNamespace
	}

	// Get the appropriate view based on policy type and namespace
	view := ps.getBarrierView(ns, policyType)
	if view == nil {
		return []string{}, fmt.Errorf("unable to get the barrier subview for policy type %q", policyType)
	}

	// Scan the view, since the policy names are the same as the
	// key names.
	var keys []string
	switch policyType {
	case PolicyTypeACL:
		keys, err = logical.CollectKeys(ctx, view)
	case PolicyTypeRGP:
		return logical.CollectKeys(ctx, view)
	case PolicyTypeEGP:
		return logical.CollectKeys(ctx, view)
	default:
		return nil, fmt.Errorf("unknown policy type %q", policyType)
	}

	// We only have non-assignable ACL policies at the moment
	for _, nonAssignable := range nonAssignablePolicies {
		deleteIndex := -1
		// Find indices of non-assignable policies in keys
		for index, key := range keys {
			if key == nonAssignable {
				// Delete collection outside the loop
				deleteIndex = index
				break
			}
		}
		// Remove non-assignable policies when found
		if deleteIndex != -1 {
			keys = append(keys[:deleteIndex], keys[deleteIndex+1:]...)
		}
	}

	return keys, err
}

// policiesByNamespace is used to list the available policies for the given namespace
func (ps *PolicyStore) policiesByNamespace(ctx context.Context, policyType PolicyType, ns *namespace.Namespace) ([]string, error) {
	var err error
	var keys []string
	var view *BarrierView

	// Scan the view, since the policy names are the same as the
	// key names.
	switch policyType {
	case PolicyTypeACL:
		view = ps.getACLView(ns)
	case PolicyTypeRGP:
		view = ps.getRGPView(ns)
	case PolicyTypeEGP:
		view = ps.getEGPView(ns)
	default:
		return nil, fmt.Errorf("unknown policy type %q", policyType)
	}

	if view == nil {
		return nil, fmt.Errorf("unable to get the barrier subview for policy type %q", policyType)
	}

	// Get the appropriate view based on policy type and namespace
	ctx = namespace.ContextWithNamespace(ctx, ns)
	keys, err = logical.CollectKeys(ctx, view)
	if err != nil {
		return nil, err
	}

	if policyType == PolicyTypeACL {
		// We only have non-assignable ACL policies at the moment
		keys = strutil.Difference(keys, nonAssignablePolicies, false)
	}

	return keys, err
}

// policiesByNamespaces is used to list the available policies for the given namespaces
func (ps *PolicyStore) policiesByNamespaces(ctx context.Context, policyType PolicyType, ns []*namespace.Namespace) ([]string, error) {
	var err error
	var keys []string

	for _, nspace := range ns {
		ks, err := ps.policiesByNamespace(ctx, policyType, nspace)
		if err != nil {
			return nil, err
		}
		keys = append(keys, ks...)
	}

	return keys, err
}

// DeletePolicyWithRequest is used to delete the named policy with a given request
func (ps *PolicyStore) DeletePolicyWithRequest(ctx context.Context, name string, policyType PolicyType, req *logical.Request) error {
	return ps.switchedDeletePolicy(ctx, name, policyType, true, false, req)
}

// DeletePolicy is used to delete the named policy
func (ps *PolicyStore) DeletePolicy(ctx context.Context, name string, policyType PolicyType) error {
	return ps.switchedDeletePolicy(ctx, name, policyType, true, false, nil)
}

// deletePolicyForce is used to delete the named policy and force it even if
// default or a singleton. It's used for invalidations or namespace deletion
// where we internally need to actually remove a policy that the user normally
// isn't allowed to remove.
func (ps *PolicyStore) deletePolicyForce(ctx context.Context, name string, policyType PolicyType) error {
	return ps.switchedDeletePolicy(ctx, name, policyType, true, true, nil)
}

func (ps *PolicyStore) switchedDeletePolicy(ctx context.Context, name string, policyType PolicyType, physicalDeletion, force bool, req *logical.Request) error {
	defer metrics.MeasureSince([]string{"policy", "delete_policy"}, time.Now())

	ns, err := namespace.FromContext(ctx)
	if err != nil {
		return err
	}
	// If not set, the call comes from invalidation, where we'll already have
	// grabbed the lock
	if physicalDeletion {
		ps.modifyLock.Lock()
		defer ps.modifyLock.Unlock()
	}

	// Policies are normalized to lower-case
	name = ps.sanitizeName(name)
	index := ps.cacheKey(ns, name)

	view := ps.getBarrierView(ns, policyType)
	if view == nil {
		return fmt.Errorf("unable to get the barrier subview for policy type %q", policyType)
	}

	switch policyType {
	case PolicyTypeACL:
		if !force {
			if strutil.StrListContains(immutablePolicies, name) {
				return fmt.Errorf("cannot delete %q policy", name)
			}
			if name == "default" {
				return fmt.Errorf("cannot delete default policy")
			}
		}

		if physicalDeletion {
			err := view.Delete(ctx, name)
			if err != nil {
				return fmt.Errorf("failed to delete policy: %w", err)
			}
		}

		if ps.tokenPoliciesLRU != nil {
			// Clear the cache
			ps.tokenPoliciesLRU.Remove(index)
		}

		ps.policyTypeMap.Delete(index)

	case PolicyTypeRGP:
		if physicalDeletion {
			err := view.Delete(ctx, name)
			if err != nil {
				return fmt.Errorf("failed to delete policy: %w", err)
			}
		}

		if ps.tokenPoliciesLRU != nil {
			// Clear the cache
			ps.tokenPoliciesLRU.Remove(index)
		}

		ps.policyTypeMap.Delete(index)

		defer ps.core.invalidateSentinelPolicy(policyType, index)

	case PolicyTypeEGP:
		if physicalDeletion {
			err := view.Delete(ctx, name)
			if err != nil {
				return fmt.Errorf("failed to delete policy: %w", err)
			}
		}

		if ps.egpLRU != nil {
			// Clear the cache
			ps.egpLRU.Remove(index)
		}

		defer ps.core.invalidateSentinelPolicy(policyType, index)

		ps.invalidateEGPTreePath(index)
	}

	var clientId string
	var entityId string
	var requestId string
	if req != nil {
		clientId = req.ClientID
		entityId = req.EntityID
		requestId = req.ID
	}
	err = ps.core.Observations().RecordObservationToLedger(ctx, observations.ObservationTypePolicyDelete, ns, map[string]interface{}{
		"client_id":     clientId,
		"entity_id":     entityId,
		"request_id":    requestId,
		"forced_delete": force,
		"type":          policyType.String(),
		"name":          name,
	})
	if err != nil {
		ps.logger.Error("error recording observation for policy delete", err)
	}

	return nil
}

// ACL is used to return an ACL which is built using the
// named policies and pre-fetched policies if given.
func (ps *PolicyStore) ACL(ctx context.Context, entity *identity.Entity, policyNames map[string][]string, additionalPolicies ...*Policy) (*ACL, error) {
	var allPolicies []*Policy

	// Fetch the named policies
	for nsID, nsPolicyNames := range policyNames {
		policyNS, err := NamespaceByID(ctx, nsID, ps.core)
		if err != nil {
			return nil, err
		}
		if policyNS == nil {
			return nil, namespace.ErrNoNamespace
		}
		policyCtx := namespace.ContextWithNamespace(ctx, policyNS)
		for _, nsPolicyName := range nsPolicyNames {
			p, err := ps.GetPolicy(policyCtx, nsPolicyName, PolicyTypeToken)
			if err != nil {
				return nil, fmt.Errorf("failed to get policy: %w", err)
			}
			if p != nil {
				allPolicies = append(allPolicies, p)
			}
		}
	}

	// Append any pre-fetched policies that were given
	allPolicies = append(allPolicies, additionalPolicies...)

	var fetchedGroups bool
	var groups []*identity.Group
	for i, policy := range allPolicies {
		if policy.Type == PolicyTypeACL && policy.Templated {
			if !fetchedGroups {
				fetchedGroups = true
				if entity != nil {
					directGroups, inheritedGroups, err := ps.core.identityStore.groupsByEntityID(entity.ID)
					if err != nil {
						return nil, fmt.Errorf("failed to fetch group memberships: %w", err)
					}
					groups = append(directGroups, inheritedGroups...)
				}
			}
			p, duplicate, err := parseACLPolicyWithTemplating(policy.namespace, policy.Raw, true, entity, groups)
			if err != nil {
				return nil, fmt.Errorf("error parsing templated policy %q: %w", policy.Name, err)
			}
			if duplicate {
				ps.logger.Warn("HCL policy contains duplicate attributes, which will no longer be supported in a future version", "policy", policy.Name, "namespace", policy.namespace.Path)
			}
			p.Name = policy.Name
			allPolicies[i] = p
		}
	}

	// Construct the ACL
	acl, err := NewACL(ctx, allPolicies)
	if err != nil {
		return nil, fmt.Errorf("failed to construct ACL: %w", err)
	}

	return acl, nil
}

// loadACLPolicy is used to load default ACL policies. The default policies will
// be loaded to all namespaces.
func (ps *PolicyStore) loadACLPolicy(ctx context.Context, policyName, policyText string) error {
	return ps.loadACLPolicyNamespaces(ctx, policyName, policyText)
}

// loadACLPolicyInternal is used to load default ACL policies in a specific
// namespace.
func (ps *PolicyStore) loadACLPolicyInternal(ctx context.Context, policyName, policyText string) error {
	ns, err := namespace.FromContext(ctx)
	if err != nil {
		return err
	}

	// Check if the policy already exists
	policy, err := ps.GetPolicy(ctx, policyName, PolicyTypeACL)
	if err != nil {
		return fmt.Errorf("error fetching %s policy from store: %w", policyName, err)
	}
	if policy != nil {
		if !strutil.StrListContains(immutablePolicies, policyName) || policyText == policy.Raw {
			return nil
		}
	}

	policy, err = ParseACLPolicy(ns, policyText)
	if err != nil {
		return fmt.Errorf("error parsing %s policy: %w", policyName, err)
	}

	if policy == nil {
		return fmt.Errorf("parsing %q policy resulted in nil policy", policyName)
	}

	policy.Name = policyName
	policy.Type = PolicyTypeACL
	return ps.setPolicyInternal(ctx, policy, nil)
}

func (ps *PolicyStore) sanitizeName(name string) string {
	return strings.ToLower(strings.TrimSpace(name))
}

func (ps *PolicyStore) cacheKey(ns *namespace.Namespace, name string) string {
	return path.Join(ns.ID, name)
}

// logDeniedParamWarning logs a warning if the given policy uses allowed_parameters or denied_parameters
// TODO (DENIED_PARAMETERS_CHANGE): Remove this function after deprecation is done
func (ps *PolicyStore) logDeniedParamWarning(p *Policy) {
	if p == nil {
		return
	}
	// check if the policy uses allowed_parameters or denied_parameters
	var usesAllowedOrDenied bool
	for _, path := range p.Paths {
		if path.Permissions != nil {
			if len(path.Permissions.AllowedParameters) > 0 || len(path.Permissions.DeniedParameters) > 0 {
				usesAllowedOrDenied = true
				break
			}
		}
	}

	if usesAllowedOrDenied {
		ps.deniedParamWarnOnce.Do(func() {
			ps.logger.Warn("you're using 'allowed_parameters' or 'denied_parameters' in one or more policies, note that Vault 1.21 introduced a behavior change. For details see https://developer.hashicorp.com/vault/docs/v1.21.x/updates/important-changes#item-by-item-list-comparison-for-allowed_parameters-and-denied_parameters")
		})
	}
}
