Guide Feishu app setup and surface tenant lookup codes
This commit is contained in:
1 parent
eb3861bbe8
commit
7e50443bba
6 files changed
+76
-5
No files matched your search
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -19,6 +20,14 @@ import (
|
||||
const feishuTenantToken = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
|
||||
const feishuTenantQuery = "https://open.feishu.cn/open-apis/tenant/v2/tenant/query"
|
||||
|
||||
// Do not include upstream msg: it is untrusted and may contain request data.
|
||||
func feishuFailure(stage string, status int, code *int) error {
|
||||
if code != nil {
|
||||
return fmt.Errorf("%s失败(飞书错误码 %d,HTTP %d);请按错误码检查应用权限、发布状态及接口配置", stage, *code, status)
|
||||
}
|
||||
return fmt.Errorf("%s失败(HTTP %d,未获得飞书错误码);请检查飞书接口配置", stage, status)
|
||||
}
|
||||
|
||||
func secureAdmin(c *gin.Context) bool {
|
||||
if c.Request.TLS != nil {
|
||||
return true
|
||||
@@ -68,8 +77,12 @@ func queryFeishuTenant(ctx context.Context, creds tenantCredentials) (string, st
|
||||
Code *int `json:"code"`
|
||||
TenantAccessToken string `json:"tenant_access_token"`
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK || json.NewDecoder(io.LimitReader(resp.Body, 1<<20)).Decode(&token) != nil || token.Code == nil || *token.Code != 0 || token.TenantAccessToken == "" {
|
||||
return "", "", errors.New("获取飞书访问凭据失败,请检查 App ID 和 App Secret")
|
||||
decodeErr := json.NewDecoder(io.LimitReader(resp.Body, 1<<20)).Decode(&token)
|
||||
if resp.StatusCode != http.StatusOK || decodeErr != nil || token.Code == nil || *token.Code != 0 {
|
||||
return "", "", feishuFailure("获取飞书访问凭据", resp.StatusCode, token.Code)
|
||||
}
|
||||
if token.TenantAccessToken == "" {
|
||||
return "", "", errors.New("飞书未返回 tenant_access_token")
|
||||
}
|
||||
req, err = http.NewRequestWithContext(ctx, http.MethodGet, feishuTenantQuery, nil)
|
||||
if err != nil {
|
||||
@@ -90,8 +103,9 @@ func queryFeishuTenant(ctx context.Context, creds tenantCredentials) (string, st
|
||||
} `json:"tenant"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK || json.NewDecoder(io.LimitReader(resp.Body, 1<<20)).Decode(&result) != nil || result.Code == nil || *result.Code != 0 {
|
||||
return "", "", errors.New("查询飞书企业信息失败,请确认应用已开通企业信息读取权限")
|
||||
decodeErr = json.NewDecoder(io.LimitReader(resp.Body, 1<<20)).Decode(&result)
|
||||
if resp.StatusCode != http.StatusOK || decodeErr != nil || result.Code == nil || *result.Code != 0 {
|
||||
return "", "", feishuFailure("查询飞书企业信息", resp.StatusCode, result.Code)
|
||||
}
|
||||
if !validTenant(result.Data.Tenant.TenantKey) {
|
||||
return "", "", errors.New("飞书未返回有效的企业标识")
|
||||
|
||||
@@ -2,6 +2,7 @@ package identityprovider
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -123,6 +124,34 @@ func TestQueryTenantRejectsWrongAppAndUpstreamFailure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTenantErrorsIdentifyStageWithoutLeakingFeishuMessage(t *testing.T) {
|
||||
previous := upstreamClient
|
||||
defer func() { upstreamClient = previous }()
|
||||
for _, tc := range []struct {
|
||||
name, endpoint, payload, want string
|
||||
status int
|
||||
}{
|
||||
{"tenant denied", feishuTenantQuery, `{"code":99991672,"msg":"secret in upstream error"}`, "99991672", 403},
|
||||
{"unexpected tenant endpoint", feishuTenantQuery, `not json`, "HTTP 404", 404},
|
||||
{"token denied", feishuTenantToken, `{"code":10003,"msg":"secret in upstream error"}`, "10003", 401},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
upstreamClient = &http.Client{Transport: transportFunc(func(req *http.Request) (*http.Response, error) {
|
||||
body := `{"code":0,"tenant_access_token":"temporary-token"}`
|
||||
status := 200
|
||||
if req.URL.String() == tc.endpoint {
|
||||
body, status = tc.payload, tc.status
|
||||
}
|
||||
return &http.Response{StatusCode: status, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}, nil
|
||||
})}
|
||||
_, _, err := queryFeishuTenant(context.Background(), tenantCredentials{AppID: "cli_test", AppSecret: "private-value"})
|
||||
if err == nil || !strings.Contains(err.Error(), tc.want) || strings.Contains(err.Error(), "secret in upstream error") || strings.Contains(err.Error(), "private-value") {
|
||||
t.Fatalf("unsafe or unhelpful error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminNeverEchoesFeishuSecretAndKeepsEncryptedValue(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("VPN_DATA_DIR", dir)
|
||||
|
||||
Reference in new issue
Block a user