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

package agent

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/hashicorp/nomad/nomad/structs"
)

func (s *HTTPServer) ACLPoliciesRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
	if req.Method != http.MethodGet {
		return nil, CodedError(405, ErrInvalidMethod)
	}

	args := structs.ACLPolicyListRequest{}
	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	var out structs.ACLPolicyListResponse
	if err := s.agent.RPC("ACL.ListPolicies", &args, &out); err != nil {
		return nil, err
	}

	setMeta(resp, &out.QueryMeta)
	if out.Policies == nil {
		out.Policies = make([]*structs.ACLPolicyListStub, 0)
	}
	return out.Policies, nil
}

func (s *HTTPServer) ACLPolicySpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
	name := strings.TrimPrefix(req.URL.Path, "/v1/acl/policy/")
	if len(name) == 0 {
		return nil, CodedError(400, "Missing Policy Name")
	}
	switch req.Method {
	case http.MethodGet:
		return s.aclPolicyQuery(resp, req, name)
	case http.MethodPut, http.MethodPost:
		return s.aclPolicyUpdate(resp, req, name)
	case http.MethodDelete:
		return s.aclPolicyDelete(resp, req, name)
	default:
		return nil, CodedError(405, ErrInvalidMethod)
	}
}

func (s *HTTPServer) aclPolicyQuery(resp http.ResponseWriter, req *http.Request,
	policyName string) (interface{}, error) {
	args := structs.ACLPolicySpecificRequest{
		Name: policyName,
	}
	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	var out structs.SingleACLPolicyResponse
	if err := s.agent.RPC("ACL.GetPolicy", &args, &out); err != nil {
		return nil, err
	}

	setMeta(resp, &out.QueryMeta)
	if out.Policy == nil {
		return nil, CodedError(404, "ACL policy not found")
	}
	return out.Policy, nil
}

func (s *HTTPServer) aclPolicyUpdate(resp http.ResponseWriter, req *http.Request,
	policyName string) (interface{}, error) {
	// Parse the policy
	var policy structs.ACLPolicy
	if err := decodeBody(req, &policy); err != nil {
		return nil, CodedError(http.StatusBadRequest, err.Error())
	}

	// Ensure the policy name matches
	if policy.Name != policyName {
		return nil, CodedError(400, "ACL policy name does not match request path")
	}

	// Format the request
	args := structs.ACLPolicyUpsertRequest{
		Policies: []*structs.ACLPolicy{&policy},
	}
	s.parseWriteRequest(req, &args.WriteRequest)

	var out structs.GenericResponse
	if err := s.agent.RPC("ACL.UpsertPolicies", &args, &out); err != nil {
		return nil, err
	}
	setIndex(resp, out.Index)
	return nil, nil
}

func (s *HTTPServer) aclPolicyDelete(resp http.ResponseWriter, req *http.Request,
	policyName string) (interface{}, error) {

	args := structs.ACLPolicyDeleteRequest{
		Names: []string{policyName},
	}
	s.parseWriteRequest(req, &args.WriteRequest)

	var out structs.GenericResponse
	if err := s.agent.RPC("ACL.DeletePolicies", &args, &out); err != nil {
		return nil, err
	}
	setIndex(resp, out.Index)
	return nil, nil
}

func (s *HTTPServer) ACLTokensRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
	if req.Method != http.MethodGet {
		return nil, CodedError(405, ErrInvalidMethod)
	}

	args := structs.ACLTokenListRequest{}
	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	var out structs.ACLTokenListResponse
	if err := s.agent.RPC("ACL.ListTokens", &args, &out); err != nil {
		return nil, err
	}

	setMeta(resp, &out.QueryMeta)
	if out.Tokens == nil {
		out.Tokens = make([]*structs.ACLTokenListStub, 0)
	}
	return out.Tokens, nil
}

func (s *HTTPServer) ACLTokenBootstrap(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
	// Ensure this is a PUT or POST
	if !(req.Method == "PUT" || req.Method == "POST") {
		return nil, CodedError(405, ErrInvalidMethod)
	}

	var args structs.ACLTokenBootstrapRequest

	if req.ContentLength != 0 {
		if err := decodeBody(req, &args); err != nil {
			return nil, CodedError(400, fmt.Sprintf("failed to decode request body: %s", err))
		}
	}

	s.parseWriteRequest(req, &args.WriteRequest)

	var out structs.ACLTokenUpsertResponse
	if err := s.agent.RPC("ACL.Bootstrap", &args, &out); err != nil {
		return nil, err
	}
	setIndex(resp, out.Index)
	if len(out.Tokens) > 0 {
		return out.Tokens[0], nil
	}
	return nil, nil
}

func (s *HTTPServer) ACLTokenSpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
	path := req.URL.Path

	switch path {
	case "/v1/acl/token":
		if !(req.Method == "PUT" || req.Method == "POST") {
			return nil, CodedError(405, ErrInvalidMethod)
		}
		return s.aclTokenUpdate(resp, req, "")
	case "/v1/acl/token/self":
		return s.aclTokenSelf(resp, req)
	}

	accessor := strings.TrimPrefix(path, "/v1/acl/token/")
	return s.aclTokenCrud(resp, req, accessor)
}

func (s *HTTPServer) aclTokenCrud(resp http.ResponseWriter, req *http.Request,
	tokenAccessor string) (interface{}, error) {
	if tokenAccessor == "" {
		return nil, CodedError(400, "Missing Token Accessor")
	}

	switch req.Method {
	case http.MethodGet:
		return s.aclTokenQuery(resp, req, tokenAccessor)
	case http.MethodPut, http.MethodPost:
		return s.aclTokenUpdate(resp, req, tokenAccessor)
	case http.MethodDelete:
		return s.aclTokenDelete(resp, req, tokenAccessor)
	default:
		return nil, CodedError(405, ErrInvalidMethod)
	}
}

func (s *HTTPServer) aclTokenQuery(resp http.ResponseWriter, req *http.Request,
	tokenAccessor string) (interface{}, error) {
	args := structs.ACLTokenSpecificRequest{
		AccessorID: tokenAccessor,
	}
	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	var out structs.SingleACLTokenResponse
	if err := s.agent.RPC("ACL.GetToken", &args, &out); err != nil {
		return nil, err
	}

	setMeta(resp, &out.QueryMeta)
	if out.Token == nil {
		return nil, CodedError(404, "ACL token not found")
	}
	return out.Token, nil
}

func (s *HTTPServer) aclTokenSelf(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
	if req.Method != http.MethodGet {
		return nil, CodedError(405, ErrInvalidMethod)
	}
	args := structs.ResolveACLTokenRequest{}
	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	args.SecretID = args.AuthToken

	var out structs.ResolveACLTokenResponse
	if err := s.agent.RPC("ACL.ResolveToken", &args, &out); err != nil {
		return nil, err
	}

	setMeta(resp, &out.QueryMeta)
	if out.Token == nil {
		return nil, CodedError(404, "ACL token not found")
	}
	return out.Token, nil
}

func (s *HTTPServer) aclTokenUpdate(resp http.ResponseWriter, req *http.Request,
	tokenAccessor string) (interface{}, error) {
	// Parse the token
	var token structs.ACLToken
	if err := decodeBody(req, &token); err != nil {
		return nil, CodedError(http.StatusBadRequest, err.Error())
	}

	// Ensure the token accessor matches
	if tokenAccessor != "" && (token.AccessorID != tokenAccessor) {
		return nil, CodedError(400, "ACL token accessor does not match request path")
	}

	// Format the request
	args := structs.ACLTokenUpsertRequest{
		Tokens: []*structs.ACLToken{&token},
	}
	s.parseWriteRequest(req, &args.WriteRequest)

	var out structs.ACLTokenUpsertResponse
	if err := s.agent.RPC("ACL.UpsertTokens", &args, &out); err != nil {
		return nil, err
	}
	setIndex(resp, out.Index)
	if len(out.Tokens) > 0 {
		return out.Tokens[0], nil
	}
	return nil, nil
}

func (s *HTTPServer) aclTokenDelete(resp http.ResponseWriter, req *http.Request,
	tokenAccessor string) (interface{}, error) {

	args := structs.ACLTokenDeleteRequest{
		AccessorIDs: []string{tokenAccessor},
	}
	s.parseWriteRequest(req, &args.WriteRequest)

	var out structs.GenericResponse
	if err := s.agent.RPC("ACL.DeleteTokens", &args, &out); err != nil {
		return nil, err
	}
	setIndex(resp, out.Index)
	return nil, nil
}

func (s *HTTPServer) UpsertOneTimeToken(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
	// Ensure this is a PUT or POST
	if !(req.Method == "PUT" || req.Method == "POST") {
		return nil, CodedError(405, ErrInvalidMethod)
	}

	// the request body is empty but we need to parse to get the auth token
	args := structs.OneTimeTokenUpsertRequest{}
	s.parseWriteRequest(req, &args.WriteRequest)

	var out structs.OneTimeTokenUpsertResponse
	if err := s.agent.RPC("ACL.UpsertOneTimeToken", &args, &out); err != nil {
		return nil, err
	}
	setIndex(resp, out.Index)
	return out, nil
}

func (s *HTTPServer) ExchangeOneTimeToken(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
	// Ensure this is a PUT or POST
	if !(req.Method == "PUT" || req.Method == "POST") {
		return nil, CodedError(405, ErrInvalidMethod)
	}

	var args structs.OneTimeTokenExchangeRequest
	if err := decodeBody(req, &args); err != nil {
		return nil, CodedError(http.StatusBadRequest, err.Error())
	}

	s.parseWriteRequest(req, &args.WriteRequest)

	var out structs.OneTimeTokenExchangeResponse
	if err := s.agent.RPC("ACL.ExchangeOneTimeToken", &args, &out); err != nil {
		return nil, err
	}
	setIndex(resp, out.Index)
	return out, nil
}

// ACLRoleListRequest performs a listing of ACL roles and is callable via the
// /v1/acl/roles HTTP API.
func (s *HTTPServer) ACLRoleListRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {

	// The endpoint only supports GET requests.
	if req.Method != http.MethodGet {
		return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
	}

	// Set up the request args and parse this to ensure the query options are
	// set.
	args := structs.ACLRolesListRequest{}

	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	// Perform the RPC request.
	var reply structs.ACLRolesListResponse
	if err := s.agent.RPC(structs.ACLListRolesRPCMethod, &args, &reply); err != nil {
		return nil, err
	}

	setMeta(resp, &reply.QueryMeta)

	if reply.ACLRoles == nil {
		reply.ACLRoles = make([]*structs.ACLRoleListStub, 0)
	}
	return reply.ACLRoles, nil
}

// ACLRoleRequest creates a new ACL role and is callable via the
// /v1/acl/role HTTP API.
func (s *HTTPServer) ACLRoleRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {

	// // The endpoint only supports PUT or POST requests.
	if !(req.Method == http.MethodPut || req.Method == http.MethodPost) {
		return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
	}

	// Use the generic upsert function without setting an ID as this will be
	// handled by the Nomad leader.
	return s.aclRoleUpsertRequest(resp, req, "")
}

// ACLRoleSpecificRequest is callable via the /v1/acl/role/ HTTP API and
// handles read via both the role name and ID, updates, and deletions.
func (s *HTTPServer) ACLRoleSpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {

	// Grab the suffix of the request, so we can further understand it.
	reqSuffix := strings.TrimPrefix(req.URL.Path, "/v1/acl/role/")

	// Split the request suffix in order to identify whether this is a lookup
	// of a service, or whether this includes a service and service identifier.
	suffixParts := strings.Split(reqSuffix, "/")

	switch len(suffixParts) {
	case 1:
		// Ensure the role ID is not an empty string which is possible if the
		// caller requested "/v1/acl/role/"
		if suffixParts[0] == "" {
			return nil, CodedError(http.StatusBadRequest, "missing ACL role ID")
		}
		return s.aclRoleRequest(resp, req, suffixParts[0])
	case 2:
		// This endpoint only supports GET.
		if req.Method != http.MethodGet {
			return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
		}

		// Ensure that the path is correct, otherwise the call could use
		// "/v1/acl/role/foobar/role-name" and successfully pass through here.
		if suffixParts[0] != "name" {
			return nil, CodedError(http.StatusBadRequest, "invalid URI")
		}

		// Ensure the role name is not an empty string which is possible if the
		// caller requested "/v1/acl/role/name/"
		if suffixParts[1] == "" {
			return nil, CodedError(http.StatusBadRequest, "missing ACL role name")
		}

		return s.aclRoleGetByNameRequest(resp, req, suffixParts[1])

	default:
		return nil, CodedError(http.StatusBadRequest, "invalid URI")
	}
}

func (s *HTTPServer) aclRoleRequest(
	resp http.ResponseWriter, req *http.Request, roleID string) (interface{}, error) {

	// Identify the method which indicates which downstream function should be
	// called.
	switch req.Method {
	case http.MethodGet:
		return s.aclRoleGetByIDRequest(resp, req, roleID)
	case http.MethodDelete:
		return s.aclRoleDeleteRequest(resp, req, roleID)
	case http.MethodPost, http.MethodPut:
		return s.aclRoleUpsertRequest(resp, req, roleID)
	default:
		return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
	}
}

func (s *HTTPServer) aclRoleGetByIDRequest(
	resp http.ResponseWriter, req *http.Request, roleID string) (interface{}, error) {

	args := structs.ACLRoleByIDRequest{
		RoleID: roleID,
	}
	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	var reply structs.ACLRoleByIDResponse
	if err := s.agent.RPC(structs.ACLGetRoleByIDRPCMethod, &args, &reply); err != nil {
		return nil, err
	}
	setMeta(resp, &reply.QueryMeta)

	if reply.ACLRole == nil {
		return nil, CodedError(http.StatusNotFound, "ACL role not found")
	}
	return reply.ACLRole, nil
}

func (s *HTTPServer) aclRoleDeleteRequest(
	resp http.ResponseWriter, req *http.Request, roleID string) (interface{}, error) {

	args := structs.ACLRolesDeleteByIDRequest{
		ACLRoleIDs: []string{roleID},
	}
	s.parseWriteRequest(req, &args.WriteRequest)

	var reply structs.ACLRolesDeleteByIDResponse
	if err := s.agent.RPC(structs.ACLDeleteRolesByIDRPCMethod, &args, &reply); err != nil {
		return nil, err
	}
	setIndex(resp, reply.Index)
	return nil, nil
}

// aclRoleUpsertRequest handles upserting an ACL to the Nomad servers. It can
// handle both new creations, and updates to existing roles.
func (s *HTTPServer) aclRoleUpsertRequest(
	resp http.ResponseWriter, req *http.Request, roleID string) (interface{}, error) {

	// Decode the ACL role.
	var aclRole structs.ACLRole
	if err := decodeBody(req, &aclRole); err != nil {
		return nil, CodedError(http.StatusBadRequest, err.Error())
	}

	// Ensure the request path ID matches the ACL role ID that was decoded.
	// Only perform this check on updates as a generic error on creation might
	// be confusing to operators as there is no specific role request path.
	if roleID != "" && roleID != aclRole.ID {
		return nil, CodedError(http.StatusBadRequest, "ACL role ID does not match request path")
	}

	args := structs.ACLRolesUpsertRequest{
		ACLRoles: []*structs.ACLRole{&aclRole},
	}
	s.parseWriteRequest(req, &args.WriteRequest)

	var out structs.ACLRolesUpsertResponse
	if err := s.agent.RPC(structs.ACLUpsertRolesRPCMethod, &args, &out); err != nil {
		return nil, err
	}
	setIndex(resp, out.Index)

	if len(out.ACLRoles) > 0 {
		return out.ACLRoles[0], nil
	}
	return nil, nil
}

func (s *HTTPServer) aclRoleGetByNameRequest(
	resp http.ResponseWriter, req *http.Request, roleName string) (interface{}, error) {

	args := structs.ACLRoleByNameRequest{
		RoleName: roleName,
	}
	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	var reply structs.ACLRoleByNameResponse
	if err := s.agent.RPC(structs.ACLGetRoleByNameRPCMethod, &args, &reply); err != nil {
		return nil, err
	}
	setMeta(resp, &reply.QueryMeta)

	if reply.ACLRole == nil {
		return nil, CodedError(http.StatusNotFound, "ACL role not found")
	}
	return reply.ACLRole, nil
}

// ACLAuthMethodListRequest performs a listing of ACL auth-methods and is
// callable via the /v1/acl/auth-methods HTTP API.
func (s *HTTPServer) ACLAuthMethodListRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {

	// The endpoint only supports GET requests.
	if req.Method != http.MethodGet {
		return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
	}

	// Set up the request args and parse this to ensure the query options are
	// set.
	args := structs.ACLAuthMethodListRequest{}

	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	// Perform the RPC request.
	var reply structs.ACLAuthMethodListResponse
	if err := s.agent.RPC(structs.ACLListAuthMethodsRPCMethod, &args, &reply); err != nil {
		return nil, err
	}

	setMeta(resp, &reply.QueryMeta)

	if reply.AuthMethods == nil {
		reply.AuthMethods = make([]*structs.ACLAuthMethodStub, 0)
	}
	return reply.AuthMethods, nil
}

// ACLAuthMethodRequest creates a new ACL auth-method and is callable via the
// /v1/acl/auth-method HTTP API.
func (s *HTTPServer) ACLAuthMethodRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {

	// // The endpoint only supports PUT or POST requests.
	if !(req.Method == http.MethodPut || req.Method == http.MethodPost) {
		return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
	}

	// Use the generic upsert function without setting an ID as this will be
	// handled by the Nomad leader.
	return s.aclAuthMethodUpsertRequest(resp, req, "")
}

// ACLAuthMethodSpecificRequest is callable via the /v1/acl/auth-method/ HTTP
// API and handles reads, updates, and deletions of named methods.
func (s *HTTPServer) ACLAuthMethodSpecificRequest(
	resp http.ResponseWriter, req *http.Request) (interface{}, error) {

	// Grab the suffix of the request, so we can further understand it.
	methodName := strings.TrimPrefix(req.URL.Path, "/v1/acl/auth-method/")

	// Ensure the auth-method name is not an empty string which is possible if
	// the caller requested "/v1/acl/role/auth-method/".
	if methodName == "" {
		return nil, CodedError(http.StatusBadRequest, "missing ACL auth-method name")
	}

	// Identify the method which indicates which downstream function should be
	// called.
	switch req.Method {
	case http.MethodGet:
		return s.aclAuthMethodGetRequest(resp, req, methodName)
	case http.MethodDelete:
		return s.aclAuthMethodDeleteRequest(resp, req, methodName)
	case http.MethodPost, http.MethodPut:
		return s.aclAuthMethodUpsertRequest(resp, req, methodName)
	default:
		return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
	}
}

// aclAuthMethodGetRequest is callable via the /v1/acl/auth-method/ HTTP API
// and is used for reading the named auth-method from state.
func (s *HTTPServer) aclAuthMethodGetRequest(
	resp http.ResponseWriter, req *http.Request, methodName string) (interface{}, error) {

	args := structs.ACLAuthMethodGetRequest{
		MethodName: methodName,
	}
	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	var reply structs.ACLAuthMethodGetResponse
	if err := s.agent.RPC(structs.ACLGetAuthMethodRPCMethod, &args, &reply); err != nil {
		return nil, err
	}
	setMeta(resp, &reply.QueryMeta)

	if reply.AuthMethod == nil {
		return nil, CodedError(http.StatusNotFound, "ACL auth-method not found")
	}
	return reply.AuthMethod, nil
}

// aclAuthMethodDeleteRequest is callable via the /v1/acl/auth-method/ HTTP API
// and is responsible for deleting the named auth-method from state.
func (s *HTTPServer) aclAuthMethodDeleteRequest(
	resp http.ResponseWriter, req *http.Request, methodName string) (interface{}, error) {

	args := structs.ACLAuthMethodDeleteRequest{
		Names: []string{methodName},
	}
	s.parseWriteRequest(req, &args.WriteRequest)

	var reply structs.ACLAuthMethodDeleteResponse
	if err := s.agent.RPC(structs.ACLDeleteAuthMethodsRPCMethod, &args, &reply); err != nil {
		return nil, err
	}
	setIndex(resp, reply.Index)
	return nil, nil
}

// aclAuthMethodUpsertRequest handles upserting an ACL auth-method to the Nomad
// servers. It can handle both new creations, and updates to existing
// auth-methods.
func (s *HTTPServer) aclAuthMethodUpsertRequest(
	resp http.ResponseWriter, req *http.Request, methodName string) (interface{}, error) {

	// Decode the ACL auth-method.
	var aclAuthMethod structs.ACLAuthMethod
	if err := decodeBody(req, &aclAuthMethod); err != nil {
		return nil, CodedError(http.StatusBadRequest, err.Error())
	}

	// Ensure the request path name matches the ACL auth-method name that was
	// decoded. Only perform this check on updates as a generic error on
	// creation might be confusing to operators as there is no specific
	// auth-method request path.
	if methodName != "" && methodName != aclAuthMethod.Name {
		return nil, CodedError(http.StatusBadRequest, "ACL auth-method name does not match request path")
	}

	args := structs.ACLAuthMethodUpsertRequest{
		AuthMethods: []*structs.ACLAuthMethod{&aclAuthMethod},
	}
	s.parseWriteRequest(req, &args.WriteRequest)

	var out structs.ACLAuthMethodUpsertResponse
	if err := s.agent.RPC(structs.ACLUpsertAuthMethodsRPCMethod, &args, &out); err != nil {
		return nil, err
	}
	setIndex(resp, out.Index)

	if len(out.AuthMethods) > 0 {
		return out.AuthMethods[0], nil
	}
	return nil, nil
}

// ACLBindingRuleListRequest performs a listing of ACL binding rules and is
// callable via the /v1/acl/binding-rules HTTP API.
func (s *HTTPServer) ACLBindingRuleListRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {

	// The endpoint only supports GET requests.
	if req.Method != http.MethodGet {
		return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
	}

	// Set up the request args and parse this to ensure the query options are
	// set.
	args := structs.ACLBindingRulesListRequest{}

	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	// Perform the RPC request.
	var reply structs.ACLBindingRulesListResponse
	if err := s.agent.RPC(structs.ACLListBindingRulesRPCMethod, &args, &reply); err != nil {
		return nil, err
	}

	setMeta(resp, &reply.QueryMeta)

	if reply.ACLBindingRules == nil {
		reply.ACLBindingRules = make([]*structs.ACLBindingRuleListStub, 0)
	}
	return reply.ACLBindingRules, nil
}

// ACLBindingRuleRequest creates a new ACL binding rule and is callable via the
// /v1/acl/binding-rule HTTP API.
func (s *HTTPServer) ACLBindingRuleRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {

	// // The endpoint only supports PUT or POST requests.
	if !(req.Method == http.MethodPut || req.Method == http.MethodPost) {
		return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
	}

	// Use the generic upsert function without setting an ID as this will be
	// handled by the Nomad leader.
	return s.aclBindingRuleUpsertRequest(resp, req, "")
}

// ACLBindingRuleSpecificRequest is callable via the /v1/acl/binding-rule/ HTTP
// API and handles read via both the ID, updates, and deletions.
func (s *HTTPServer) ACLBindingRuleSpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {

	// Grab the suffix of the request, so we can further understand it.
	reqSuffix := strings.TrimPrefix(req.URL.Path, "/v1/acl/binding-rule/")

	// Ensure the binding rule ID is not an empty string which is possible if
	// the caller requested "/v1/acl/role/binding-rule/".
	if reqSuffix == "" {
		return nil, CodedError(http.StatusBadRequest, "missing ACL binding rule ID")
	}

	// Identify the HTTP method which indicates which downstream function
	// should be called.
	switch req.Method {
	case http.MethodGet:
		return s.aclBindingRuleGetRequest(resp, req, reqSuffix)
	case http.MethodDelete:
		return s.aclBindingRuleDeleteRequest(resp, req, reqSuffix)
	case http.MethodPost, http.MethodPut:
		return s.aclBindingRuleUpsertRequest(resp, req, reqSuffix)
	default:
		return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
	}
}

func (s *HTTPServer) aclBindingRuleGetRequest(
	resp http.ResponseWriter, req *http.Request, ruleID string) (interface{}, error) {

	args := structs.ACLBindingRuleRequest{
		ACLBindingRuleID: ruleID,
	}
	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
		return nil, nil
	}

	var reply structs.ACLBindingRuleResponse
	if err := s.agent.RPC(structs.ACLGetBindingRuleRPCMethod, &args, &reply); err != nil {
		return nil, err
	}
	setMeta(resp, &reply.QueryMeta)

	if reply.ACLBindingRule == nil {
		return nil, CodedError(http.StatusNotFound, "ACL binding rule not found")
	}
	return reply.ACLBindingRule, nil
}

func (s *HTTPServer) aclBindingRuleDeleteRequest(
	resp http.ResponseWriter, req *http.Request, ruleID string) (interface{}, error) {

	args := structs.ACLBindingRulesDeleteRequest{
		ACLBindingRuleIDs: []string{ruleID},
	}
	s.parseWriteRequest(req, &args.WriteRequest)

	var reply structs.ACLBindingRulesDeleteResponse
	if err := s.agent.RPC(structs.ACLDeleteBindingRulesRPCMethod, &args, &reply); err != nil {
		return nil, err
	}
	setIndex(resp, reply.Index)
	return nil, nil
}

// aclBindingRuleUpsertRequest handles upserting an ACL binding rule to the
// Nomad servers. It can handle both new creations, and updates to existing
// rules.
func (s *HTTPServer) aclBindingRuleUpsertRequest(
	resp http.ResponseWriter, req *http.Request, ruleID string) (interface{}, error) {

	// Decode the ACL binding rule.
	var aclBindingRule structs.ACLBindingRule
	if err := decodeBody(req, &aclBindingRule); err != nil {
		return nil, CodedError(http.StatusBadRequest, err.Error())
	}

	// If the request path includes an ID, ensure the payload has an ID if it
	// has been left empty.
	if ruleID != "" && aclBindingRule.ID == "" {
		aclBindingRule.ID = ruleID
	}

	// Ensure the request path ID matches the ACL binding rule ID that was
	// decoded. Only perform this check on updates as a generic error on
	// creation might be confusing to operators as there is no specific binding
	// rule request path.
	if ruleID != "" && ruleID != aclBindingRule.ID {
		return nil, CodedError(http.StatusBadRequest, "ACL binding rule ID does not match request path")
	}

	args := structs.ACLBindingRulesUpsertRequest{
		ACLBindingRules: []*structs.ACLBindingRule{&aclBindingRule},
	}
	s.parseWriteRequest(req, &args.WriteRequest)

	var out structs.ACLBindingRulesUpsertResponse
	if err := s.agent.RPC(structs.ACLUpsertBindingRulesRPCMethod, &args, &out); err != nil {
		return nil, err
	}
	setIndex(resp, out.Index)

	if len(out.ACLBindingRules) > 0 {
		return out.ACLBindingRules[0], nil
	}
	return nil, nil
}

// ACLOIDCAuthURLRequest starts the OIDC login workflow.
func (s *HTTPServer) ACLOIDCAuthURLRequest(_ http.ResponseWriter, req *http.Request) (interface{}, error) {

	// The endpoint only supports PUT or POST requests.
	if req.Method != http.MethodPost && req.Method != http.MethodPut {
		return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
	}

	var args structs.ACLOIDCAuthURLRequest
	s.parseWriteRequest(req, &args.WriteRequest)

	if err := decodeBody(req, &args); err != nil {
		return nil, CodedError(http.StatusBadRequest, err.Error())
	}

	var out structs.ACLOIDCAuthURLResponse
	if err := s.agent.RPC(structs.ACLOIDCAuthURLRPCMethod, &args, &out); err != nil {
		return nil, err
	}
	return out, nil
}

// ACLOIDCCompleteAuthRequest completes the OIDC login workflow.
func (s *HTTPServer) ACLOIDCCompleteAuthRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {

	// The endpoint only supports PUT or POST requests.
	if req.Method != http.MethodPost && req.Method != http.MethodPut {
		return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
	}

	var args structs.ACLOIDCCompleteAuthRequest
	s.parseWriteRequest(req, &args.WriteRequest)

	if err := decodeBody(req, &args); err != nil {
		return nil, CodedError(http.StatusBadRequest, err.Error())
	}

	var out structs.ACLLoginResponse
	if err := s.agent.RPC(structs.ACLOIDCCompleteAuthRPCMethod, &args, &out); err != nil {
		return nil, err
	}
	setIndex(resp, out.Index)
	return out.ACLToken, nil
}

// ACLLoginRequest performs a non-interactive authentication request
func (s *HTTPServer) ACLLoginRequest(resp http.ResponseWriter, req *http.Request) (any, error) {
	// The endpoint only supports PUT or POST requests.
	if req.Method != http.MethodPost && req.Method != http.MethodPut {
		return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
	}
	var args structs.ACLLoginRequest
	s.parseWriteRequest(req, &args.WriteRequest)
	if err := decodeBody(req, &args); err != nil {
		return nil, CodedError(http.StatusBadRequest, err.Error())
	}
	var out structs.ACLLoginResponse
	if err := s.agent.RPC(structs.ACLLoginRPCMethod, &args, &out); err != nil {
		return nil, err
	}
	setIndex(resp, out.Index)
	return out.ACLToken, nil
}
