mirror of
https://github.com/larksuite/cli.git
synced 2026-07-06 16:18:05 +08:00
When AutoGrantCurrentUserDrivePermission encounters lark code 99991672/99991679, extract permission_violations from the underlying ExitError and surface lark_code, required_scope, and console_url on the result map. Override the generic fallback hint with one pointing at the developer console — the concrete next step a user can take. Refactor extractRequiredScopes / SelectRecommendedScope wrapping / console URL construction out of cmd/root.go into internal/registry/scope_hint.go so both the top-level enrichPermissionError path and the best-effort sub-call path in shortcuts/common share one implementation. Change-Id: Ida63ed160d1167b7961b6faac5c2cf9b7f971c65
83 lines
2.3 KiB
Go
83 lines
2.3 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package registry
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
|
|
"github.com/larksuite/cli/internal/core"
|
|
)
|
|
|
|
// ExtractRequiredScopes pulls scope names out of the API error's
|
|
// permission_violations field. The detail argument is the raw `error` block
|
|
// that the platform returns alongside lark code 99991672 / 99991679 — typically
|
|
// shaped as:
|
|
//
|
|
// { "permission_violations": [ {"subject": "<scope>"}, ... ] }
|
|
//
|
|
// Returns nil when the structure does not match or no non-empty subjects are
|
|
// present, so callers can branch on a simple len() == 0 check.
|
|
func ExtractRequiredScopes(detail interface{}) []string {
|
|
m, ok := detail.(map[string]interface{})
|
|
if !ok {
|
|
return nil
|
|
}
|
|
violations, ok := m["permission_violations"].([]interface{})
|
|
if !ok {
|
|
return nil
|
|
}
|
|
scopes := make([]string, 0, len(violations))
|
|
for _, v := range violations {
|
|
vm, ok := v.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
if subject, ok := vm["subject"].(string); ok && subject != "" {
|
|
scopes = append(scopes, subject)
|
|
}
|
|
}
|
|
if len(scopes) == 0 {
|
|
return nil
|
|
}
|
|
return scopes
|
|
}
|
|
|
|
// SelectRecommendedScopeFromStrings is a string-typed convenience wrapper
|
|
// around SelectRecommendedScope. When no scope is recognized by the priority
|
|
// table, it falls back to the first input scope so callers always have
|
|
// something to surface to users.
|
|
func SelectRecommendedScopeFromStrings(scopes []string, identity string) string {
|
|
if len(scopes) == 0 {
|
|
return ""
|
|
}
|
|
ifaces := make([]interface{}, len(scopes))
|
|
for i, s := range scopes {
|
|
ifaces[i] = s
|
|
}
|
|
if recommended := SelectRecommendedScope(ifaces, identity); recommended != "" {
|
|
return recommended
|
|
}
|
|
return scopes[0]
|
|
}
|
|
|
|
// BuildConsoleScopeURL returns the developer-console "apply scope" URL for the
|
|
// given app and scope, branded for feishu / lark. Returns "" when appID or
|
|
// scope is empty so callers can omit the field cleanly.
|
|
func BuildConsoleScopeURL(brand core.LarkBrand, appID, scope string) string {
|
|
if appID == "" || scope == "" {
|
|
return ""
|
|
}
|
|
host := "open.feishu.cn"
|
|
if brand == core.BrandLark {
|
|
host = "open.larksuite.com"
|
|
}
|
|
return fmt.Sprintf(
|
|
"https://%s/page/scope-apply?clientID=%s&scopes=%s",
|
|
host,
|
|
url.QueryEscape(appID),
|
|
url.QueryEscape(scope),
|
|
)
|
|
}
|