mirror of
https://github.com/larksuite/cli.git
synced 2026-09-14 18:42:53 +08:00
112 lines
3.2 KiB
Go
112 lines
3.2 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package cmdpolicy
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/larksuite/cli/extension/platform"
|
|
)
|
|
|
|
// ActivePolicy is the resolved user-layer policy after applyUserPolicyPruning
|
|
// has run during bootstrap. `lark-cli config policy show` reads this to
|
|
// answer "what rule is currently in effect, and how many commands does
|
|
// it hide?".
|
|
//
|
|
// Set once at bootstrap time; consumed read-only thereafter.
|
|
//
|
|
// Rules is the full set the winning source contributed (one rule for the
|
|
// common single-rule case, several when a plugin or yaml declares scoped
|
|
// grants). nil/empty means "no rule applied".
|
|
type ActivePolicy struct {
|
|
Rules []*platform.Rule
|
|
Source ResolveSource
|
|
|
|
// DeniedByPath is the full post-aggregation denial map.
|
|
DeniedByPath map[string]Denial
|
|
}
|
|
|
|
// DeniedPathCount returns the number of post-aggregation denied commands.
|
|
// Deriving it from DeniedByPath prevents diagnostics from drifting away from
|
|
// the exact denial snapshot used by presentation.
|
|
func (p *ActivePolicy) DeniedPathCount() int {
|
|
if p == nil {
|
|
return 0
|
|
}
|
|
return len(p.DeniedByPath)
|
|
}
|
|
|
|
var (
|
|
activeMu sync.RWMutex
|
|
activePolicy *ActivePolicy
|
|
)
|
|
|
|
// SetActive records the policy that ends up applied. Called exactly once
|
|
// per process from cmd/policy.go::applyUserPolicyPruning. The mutex is
|
|
// belt-and-braces in case future test paths interleave with bootstrap.
|
|
//
|
|
// A deep copy is taken so the snapshot is immune to later mutations of
|
|
// the input by the caller (a plugin-supplied *Rule could otherwise
|
|
// mutate the embedded Allow/Deny/Identities slices after we stored it).
|
|
func SetActive(p *ActivePolicy) {
|
|
activeMu.Lock()
|
|
defer activeMu.Unlock()
|
|
if p == nil {
|
|
activePolicy = nil
|
|
return
|
|
}
|
|
activePolicy = cloneActivePolicy(p)
|
|
}
|
|
|
|
// GetActive returns a deep copy of the recorded policy, or nil if
|
|
// bootstrap has not finished or no rule applied. Callers can freely
|
|
// mutate the result — including the embedded Rule slices — without
|
|
// affecting the stored global.
|
|
func GetActive() *ActivePolicy {
|
|
activeMu.RLock()
|
|
defer activeMu.RUnlock()
|
|
if activePolicy == nil {
|
|
return nil
|
|
}
|
|
return cloneActivePolicy(activePolicy)
|
|
}
|
|
|
|
// cloneActivePolicy deep-copies the top-level struct, the Rules slice, and
|
|
// each Rule's own slice fields. Source is a value type, so the struct copy
|
|
// already disjoints it.
|
|
func cloneActivePolicy(in *ActivePolicy) *ActivePolicy {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
cp := *in
|
|
if in.Rules != nil {
|
|
cp.Rules = make([]*platform.Rule, len(in.Rules))
|
|
for i, r := range in.Rules {
|
|
if r == nil {
|
|
continue
|
|
}
|
|
rule := *r
|
|
rule.Allow = append([]string(nil), r.Allow...)
|
|
rule.Deny = append([]string(nil), r.Deny...)
|
|
rule.Identities = append([]platform.Identity(nil), r.Identities...)
|
|
cp.Rules[i] = &rule
|
|
}
|
|
}
|
|
if in.DeniedByPath != nil {
|
|
cp.DeniedByPath = make(map[string]Denial, len(in.DeniedByPath))
|
|
for k, v := range in.DeniedByPath {
|
|
cp.DeniedByPath[k] = v
|
|
}
|
|
}
|
|
return &cp
|
|
}
|
|
|
|
// ResetActiveForTesting clears the recorded policy. Tests must call this
|
|
// in t.Cleanup when they exercise the bootstrap path.
|
|
func ResetActiveForTesting() {
|
|
activeMu.Lock()
|
|
defer activeMu.Unlock()
|
|
activePolicy = nil
|
|
}
|