* dws event * fix consume fail * test: add stream ticket injection probe * feat: add portal ticket stream mode * user event * fix: allow portal ticket normal without app secret * event * user event * eventType filter * refactor(event): 优化IPC端点路径处理和改进相关测试 - 用dwsevent.IPCEndpoint替代原先根据GOOS判断的路径逻辑 - 新增event包实现Unix socket路径长度限制及长路径fallback机制 - 添加endpoint_test.go覆盖路径短长及唯一性的单元测试 - 修改busctl模块使用统一的IPC端点获取方法,避免重复实现 - transport_unix.go新增checkSocketPath函数检查路径长度,防止EINVAL错误 - 在监听和连接Unix socket时加入路径限制检查,提升错误明晰度 - 去除多个文件中无用的runtime导入,简化代码依赖 * opt * event skill * default value * install script event * fix: remove subscribe id event fanout filter * fix(personal): 修正指定发送人消息描述错误 * more im event * filter subId * fix: align personal event schema with stream payload * fix: avoid duplicate app helper name * feat: simplify personal event schemas * feat: simplify event schema output * docs: refine dingtalk event skill references * feat: align personal event consume flags * fix event stop and status visibility * hide app event public entrypoints * hide incomplete personal sender event * remove external event reference comments * chore(event): prepare official release * fix(event): harden personal stream lifecycle --------- Co-authored-by: 玉澜 <yulan.wqy@alibaba-inc.com> Co-authored-by: zhengyubai <zhengyubai618@gmail.com>
158 lines
6.5 KiB
Go
158 lines
6.5 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 auth
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
)
|
|
|
|
// CredentialSource identifies where a particular credential field
|
|
// (ClientID or ClientSecret) was loaded from. It is exposed in
|
|
// `dws event status` and the HelloAck IPC frame so users can verify which
|
|
// credential channel is actually in use — important because env vars,
|
|
// keychain, and config file can all coexist and silently override each
|
|
// other (see plan §1 决策 "凭证来源拆字段").
|
|
type CredentialSource string
|
|
|
|
const (
|
|
CredentialSourceUnknown CredentialSource = "unknown"
|
|
CredentialSourceEnv CredentialSource = "env"
|
|
CredentialSourceAppConfig CredentialSource = "app_config" // value pulled from app config (plain or SecretRef metadata)
|
|
CredentialSourceKeychain CredentialSource = "keychain" // SecretRef resolved through OS keychain
|
|
CredentialSourcePlainConfig CredentialSource = "plain_config" // SecretInput stored as plaintext in config file (insecure but supported)
|
|
)
|
|
|
|
// Strict resolver error sentinels. Use errors.Is to distinguish failure
|
|
// modes; see plan §8 strict resolver decision (4 classes).
|
|
var (
|
|
// ErrAppConfigMissing — no app config file on disk AND no env-var
|
|
// credentials present. Prompt the user to either `dws config init` or
|
|
// set DWS_CLIENT_ID + DWS_CLIENT_SECRET.
|
|
ErrAppConfigMissing = errors.New("app config missing: run `dws config init` or set DWS_CLIENT_ID/DWS_CLIENT_SECRET env vars")
|
|
// ErrClientIDEmpty — neither env nor config supplies a non-empty ClientID.
|
|
ErrClientIDEmpty = errors.New("ClientID is empty")
|
|
// ErrClientSecretEmpty — there's a ClientID but ClientSecret resolved to "".
|
|
ErrClientSecretEmpty = errors.New("ClientSecret is empty")
|
|
// ErrSecretResolve — the secret-resolution backend (keychain) failed
|
|
// unrecoverably. Typically headless Linux without gnome-keyring, locked
|
|
// macOS keychain, or CI sandboxes. Suggest the env-var fallback.
|
|
ErrSecretResolve = errors.New("ClientSecret resolution failed (keychain unavailable?); try DWS_CLIENT_ID/DWS_CLIENT_SECRET env vars")
|
|
)
|
|
|
|
// Env var names used by the env fallback channel. Must be set as a pair —
|
|
// any single-variable configuration is rejected so users cannot accidentally
|
|
// "set the env half-way" and silently fall back to keychain.
|
|
const (
|
|
EnvClientID = "DWS_CLIENT_ID"
|
|
EnvClientSecret = "DWS_CLIENT_SECRET"
|
|
)
|
|
|
|
// ResolveAppCredentialsStrict is the credentials channel used by the event
|
|
// subsystem (and by future commands that need fine-grained failure
|
|
// reporting). It distinguishes 4 failure classes and reports the source of
|
|
// each successfully-resolved field separately.
|
|
//
|
|
// Resolution order:
|
|
// 1. Env var override: if BOTH DWS_CLIENT_ID and DWS_CLIENT_SECRET are
|
|
// set non-empty, use them as a pair and skip keychain/config entirely.
|
|
// Single-variable configuration is detected and reported via the
|
|
// EnvHalfSet flag in the warning channel (callers MAY log a warning).
|
|
// 2. App config from disk:
|
|
// - ClientID from cfg.ClientID
|
|
// - ClientSecret from ResolveSecret(cfg.ClientSecret):
|
|
// - SecretInput.IsPlain() → CredentialSourcePlainConfig
|
|
// - SecretRef → CredentialSourceKeychain (or whatever Ref.Source says)
|
|
//
|
|
// Empty returns: clientID and secret may be empty when err is non-nil;
|
|
// callers must NOT use them in that case.
|
|
func ResolveAppCredentialsStrict(configDir string) (
|
|
clientID, secret string,
|
|
clientIDSource, secretSource CredentialSource,
|
|
err error,
|
|
) {
|
|
// Step 1: env var fallback (atomic pair)
|
|
envID := os.Getenv(EnvClientID)
|
|
envSecret := os.Getenv(EnvClientSecret)
|
|
if envID != "" && envSecret != "" {
|
|
return envID, envSecret, CredentialSourceEnv, CredentialSourceEnv, nil
|
|
}
|
|
// Note: if only one of the two is set we explicitly do NOT use it.
|
|
// The half-set warning is surfaced via EnvHalfSet() so the CLI can
|
|
// stderr-warn the user during preflight.
|
|
|
|
// Step 2: app config from disk
|
|
cfg, loadErr := LoadAppConfig(configDir)
|
|
if loadErr != nil {
|
|
return "", "", CredentialSourceUnknown, CredentialSourceUnknown,
|
|
fmt.Errorf("load app config: %w", loadErr)
|
|
}
|
|
if cfg == nil {
|
|
return "", "", CredentialSourceUnknown, CredentialSourceUnknown, ErrAppConfigMissing
|
|
}
|
|
|
|
if cfg.ClientID == "" {
|
|
return "", "", CredentialSourceUnknown, CredentialSourceUnknown, ErrClientIDEmpty
|
|
}
|
|
clientID = cfg.ClientID
|
|
clientIDSource = CredentialSourceAppConfig
|
|
|
|
// Resolve secret. Source depends on the SecretInput shape:
|
|
// - IsPlain (no Ref) → it's stored as plaintext in the config file
|
|
// - has Ref → it's a SecretRef pointing at keychain/file
|
|
wasPlain := cfg.ClientSecret.IsPlain()
|
|
resolved, resolveErr := ResolveSecret(cfg.ClientSecret)
|
|
if resolveErr != nil {
|
|
return "", "", CredentialSourceUnknown, CredentialSourceUnknown,
|
|
fmt.Errorf("%w: %v", ErrSecretResolve, resolveErr)
|
|
}
|
|
if resolved == "" {
|
|
return "", "", clientIDSource, CredentialSourceUnknown, ErrClientSecretEmpty
|
|
}
|
|
|
|
secret = resolved
|
|
if wasPlain {
|
|
secretSource = CredentialSourcePlainConfig
|
|
} else {
|
|
// For SecretRef we map Source verbatim (keychain / file / future)
|
|
switch cfg.ClientSecret.Ref.Source {
|
|
case "keychain":
|
|
secretSource = CredentialSourceKeychain
|
|
default:
|
|
// File-backed secrets share the "plain_config" category from
|
|
// the consumer's perspective: stored as readable bytes outside
|
|
// keychain. Status output renders them as "plain_config" so
|
|
// users see "secret is not in keychain".
|
|
secretSource = CredentialSourcePlainConfig
|
|
}
|
|
}
|
|
|
|
return clientID, secret, clientIDSource, secretSource, nil
|
|
}
|
|
|
|
// EnvHalfSet reports whether exactly one of (DWS_CLIENT_ID, DWS_CLIENT_SECRET)
|
|
// is set. Used by CLI preflight to emit a clear stderr warning of the form:
|
|
//
|
|
// WARN: DWS_CLIENT_ID is set but DWS_CLIENT_SECRET is not — env fallback
|
|
// disabled; using keychain/app config. Set both or unset both to
|
|
// avoid this warning.
|
|
//
|
|
// The strict resolver itself does NOT log; logging is the caller's job.
|
|
func EnvHalfSet() bool {
|
|
id := os.Getenv(EnvClientID) != ""
|
|
secret := os.Getenv(EnvClientSecret) != ""
|
|
return id != secret
|
|
}
|