261 lines
7.4 KiB
Go
261 lines
7.4 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package pat
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/helpers"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/output"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition"
|
|
)
|
|
|
|
const browserPolicyFile = "pat_policy.json"
|
|
|
|
var (
|
|
patPolicyReadFile = os.ReadFile
|
|
patPolicyUserHomeDir = os.UserHomeDir
|
|
patPolicyMarshal = json.MarshalIndent
|
|
patPolicyAtomicWrite = helpers.AtomicWriteJSON
|
|
)
|
|
|
|
type browserPolicyValue struct {
|
|
OpenBrowser bool `json:"openBrowser"`
|
|
}
|
|
|
|
type BrowserPolicy struct {
|
|
Default *browserPolicyValue `json:"default,omitempty"`
|
|
Agents map[string]browserPolicyValue `json:"agents,omitempty"`
|
|
}
|
|
|
|
type BrowserPolicySelection struct {
|
|
Scope string `json:"scope"`
|
|
AgentCode string `json:"agentCode,omitempty"`
|
|
OpenBrowser bool `json:"openBrowser"`
|
|
Source string `json:"source"`
|
|
}
|
|
|
|
func patPolicyPath(configDir string) string {
|
|
return filepath.Join(configDir, browserPolicyFile)
|
|
}
|
|
|
|
func patConfigDir() string {
|
|
if envDir := os.Getenv("DWS_CONFIG_DIR"); envDir != "" {
|
|
return envDir
|
|
}
|
|
if fn := edition.Get().ConfigDir; fn != nil {
|
|
return fn()
|
|
}
|
|
homeDir, err := patPolicyUserHomeDir()
|
|
if err != nil {
|
|
return ".dws"
|
|
}
|
|
return filepath.Join(homeDir, ".dws")
|
|
}
|
|
|
|
func LoadBrowserPolicy(configDir string) (*BrowserPolicy, error) {
|
|
path := patPolicyPath(configDir)
|
|
data, err := patPolicyReadFile(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return &BrowserPolicy{}, nil
|
|
}
|
|
return nil, fmt.Errorf("reading PAT browser policy: %w", err)
|
|
}
|
|
|
|
var policy BrowserPolicy
|
|
if err := json.Unmarshal(data, &policy); err != nil {
|
|
return nil, fmt.Errorf("parsing PAT browser policy: %w", err)
|
|
}
|
|
if policy.Agents == nil {
|
|
policy.Agents = map[string]browserPolicyValue{}
|
|
}
|
|
return &policy, nil
|
|
}
|
|
|
|
func saveBrowserPolicy(configDir string, policy *BrowserPolicy) error {
|
|
if policy == nil {
|
|
policy = &BrowserPolicy{}
|
|
}
|
|
if policy.Agents == nil {
|
|
policy.Agents = map[string]browserPolicyValue{}
|
|
}
|
|
data, err := patPolicyMarshal(policy, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("marshaling PAT browser policy: %w", err)
|
|
}
|
|
data = append(data, '\n')
|
|
if err := patPolicyAtomicWrite(patPolicyPath(configDir), data); err != nil {
|
|
return fmt.Errorf("writing PAT browser policy: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ResolveBrowserPolicy(configDir, explicitAgentCode string) (BrowserPolicySelection, error) {
|
|
agentCode, err := resolveAgentCode(explicitAgentCode)
|
|
if err != nil {
|
|
return BrowserPolicySelection{}, err
|
|
}
|
|
policy, err := LoadBrowserPolicy(configDir)
|
|
if err != nil {
|
|
return BrowserPolicySelection{}, err
|
|
}
|
|
|
|
if agentCode != "" {
|
|
if entry, ok := policy.Agents[agentCode]; ok {
|
|
return BrowserPolicySelection{
|
|
Scope: "agent",
|
|
AgentCode: agentCode,
|
|
OpenBrowser: entry.OpenBrowser,
|
|
Source: "agent",
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
if policy.Default != nil {
|
|
return BrowserPolicySelection{
|
|
Scope: "default",
|
|
OpenBrowser: policy.Default.OpenBrowser,
|
|
Source: "default",
|
|
}, nil
|
|
}
|
|
|
|
return BrowserPolicySelection{
|
|
Scope: "builtin_default",
|
|
AgentCode: agentCode,
|
|
OpenBrowser: true,
|
|
Source: "builtin_default",
|
|
}, nil
|
|
}
|
|
|
|
func EffectiveOpenBrowser(configDir string) bool {
|
|
selection, err := ResolveBrowserPolicy(configDir, "")
|
|
if err != nil {
|
|
return true
|
|
}
|
|
return selection.OpenBrowser
|
|
}
|
|
|
|
func resolveBrowserPolicyWriteAgentCode(explicitAgentCode string) (string, error) {
|
|
agentCode := strings.TrimSpace(explicitAgentCode)
|
|
if agentCode == "" {
|
|
return "", nil
|
|
}
|
|
if err := validateAgentCode(agentCode); err != nil {
|
|
return "", err
|
|
}
|
|
return agentCode, nil
|
|
}
|
|
|
|
func SetBrowserPolicy(configDir, explicitAgentCode string, enabled bool) (BrowserPolicySelection, error) {
|
|
agentCode, err := resolveBrowserPolicyWriteAgentCode(explicitAgentCode)
|
|
if err != nil {
|
|
return BrowserPolicySelection{}, err
|
|
}
|
|
|
|
policy, err := LoadBrowserPolicy(configDir)
|
|
if err != nil {
|
|
return BrowserPolicySelection{}, err
|
|
}
|
|
if policy.Agents == nil {
|
|
policy.Agents = map[string]browserPolicyValue{}
|
|
}
|
|
|
|
if agentCode != "" {
|
|
policy.Agents[agentCode] = browserPolicyValue{OpenBrowser: enabled}
|
|
if err := saveBrowserPolicy(configDir, policy); err != nil {
|
|
return BrowserPolicySelection{}, err
|
|
}
|
|
return BrowserPolicySelection{
|
|
Scope: "agent",
|
|
AgentCode: agentCode,
|
|
OpenBrowser: enabled,
|
|
Source: "agent",
|
|
}, nil
|
|
}
|
|
|
|
policy.Default = &browserPolicyValue{OpenBrowser: enabled}
|
|
if err := saveBrowserPolicy(configDir, policy); err != nil {
|
|
return BrowserPolicySelection{}, err
|
|
}
|
|
return BrowserPolicySelection{
|
|
Scope: "default",
|
|
OpenBrowser: enabled,
|
|
Source: "default",
|
|
}, nil
|
|
}
|
|
|
|
func newBrowserPolicyCommand() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "browser-policy",
|
|
Short: "配置 PAT 授权时是否打开浏览器",
|
|
DisableAutoGenTag: true,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if !cmd.Flags().Changed("enabled") {
|
|
return fmt.Errorf("--enabled is required")
|
|
}
|
|
|
|
enabled, _ := cmd.Flags().GetBool("enabled")
|
|
agentCode, _ := cmd.Flags().GetString("agentCode")
|
|
|
|
selection, err := SetBrowserPolicy(patConfigDir(), agentCode, enabled)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return output.WriteJSON(cmd.OutOrStdout(), selection)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().Bool("enabled", false, "PAT 撞墙时是否允许本地打开浏览器")
|
|
cmd.Flags().String("agentCode", "", "Agent 唯一标识(可选;不填则写入全局默认策略,不从 env DINGTALK_DWS_AGENTCODE 回退)")
|
|
helpers.DeclareLeafMetadata(cmd, helpers.LeafSpec{
|
|
Safety: contract.SafetySpec{
|
|
Effect: "write", Risk: "medium",
|
|
Confirmation: "not_required", Idempotency: "unknown",
|
|
},
|
|
Contract: helpers.LeafContract{
|
|
Identity: contract.ToolIdentitySpec{
|
|
ProductID: "pat",
|
|
Name: "browser_policy",
|
|
CanonicalPath: "pat.browser_policy",
|
|
CLIPath: "pat browser-policy",
|
|
PrimaryCLIPath: "pat browser-policy",
|
|
},
|
|
Description: "配置 PAT 授权流程是否允许打开本地浏览器",
|
|
Interface: &contract.InterfaceSpec{
|
|
Mode: "local",
|
|
Availability: "available",
|
|
Reason: "命令仅操作本地进程或策略文件,不调用 MCP 接口",
|
|
},
|
|
Selection: contract.SelectionSpec{
|
|
AgentSummary: "配置 PAT 授权流程是否允许打开本地浏览器",
|
|
UseWhen: []string{"需要允许或禁止某 Agent 在 PAT 授权时打开浏览器"},
|
|
AvoidWhen: []string{"需要授予产品 scope 时用 pat chmod,而不是改浏览器策略"},
|
|
Examples: []string{
|
|
"dws pat browser-policy --enabled --format json",
|
|
"dws pat browser-policy --enabled=false --agentCode <AGENT_CODE> --format json",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
return cmd
|
|
}
|