Files
yunzerwebsiteallinone/go/controllers/platform_auth_idp.go
T
2026-09-20 00:19:08 +08:00

258 lines
7.4 KiB
Go

package controllers
import (
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"server/models"
"server/pkg/jwtutil"
beego "github.com/beego/beego/v2/server/web"
)
// PlatformAuthIdpController 统一认证 —— 第三方登录配置(平台端管理)
//
// 配置说明:
// - tid = 0 :平台全局配置,所有租户共用
// - tid > 0 :租户自带身份源,仅该租户可见(第10条需求:企业自带钉钉/飞书等)
//
// 配置对象只有「租户用户登录」,平台端自身不使用第三方登录。
type PlatformAuthIdpController struct {
beego.Controller
}
func (c *PlatformAuthIdpController) serveJSON(data map[string]interface{}) {
c.Data["json"] = data
_ = c.ServeJSON()
}
// Prepare 统一鉴权:仅平台管理员可维护第三方登录配置
func (c *PlatformAuthIdpController) Prepare() {
authHeader := c.Ctx.Request.Header.Get("Authorization")
if authHeader == "" {
c.Ctx.Output.SetStatus(401)
c.serveJSON(map[string]interface{}{"code": 401, "msg": "未登录"})
c.StopRun()
return
}
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
c.Ctx.Output.SetStatus(401)
c.serveJSON(map[string]interface{}{"code": 401, "msg": "认证信息格式错误"})
c.StopRun()
return
}
claims, err := jwtutil.ParseToken(parts[1])
if err != nil {
c.Ctx.Output.SetStatus(401)
c.serveJSON(map[string]interface{}{"code": 401, "msg": "登录已失效,请重新登录"})
c.StopRun()
return
}
if claims.UserType != "platform" {
c.Ctx.Output.SetStatus(403)
c.serveJSON(map[string]interface{}{"code": 403, "msg": "无权访问"})
c.StopRun()
return
}
}
type authIdpPayload struct {
Tid uint64 `json:"tid"`
Provider string `json:"provider"`
Name string `json:"name"`
AppID string `json:"app_id"`
AppSecret string `json:"app_secret"`
ProxyURL string `json:"proxy_url"`
Scopes string `json:"scopes"`
AuthURL string `json:"auth_url"`
TokenURL string `json:"token_url"`
UserinfoURL string `json:"userinfo_url"`
Status *int8 `json:"status"`
}
func (c *PlatformAuthIdpController) parsePayload() (authIdpPayload, bool) {
var p authIdpPayload
raw, _ := io.ReadAll(c.Ctx.Request.Body)
if len(raw) > 0 {
if err := json.Unmarshal(raw, &p); err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"})
return p, false
}
}
return p, true
}
// List 配置列表
// GET /platform/authIdp/list?tid=0
func (c *PlatformAuthIdpController) List() {
tid, _ := c.GetInt64("tid", 0)
var rows []models.AuthTenantIdp
if _, err := models.Orm.QueryTable(new(models.AuthTenantIdp)).
Filter("tid", tid).OrderBy("id").All(&rows); err != nil {
c.serveJSON(map[string]interface{}{"code": 500, "msg": "查询失败: " + err.Error()})
return
}
// 脱敏:不下发 AppSecret
list := make([]map[string]interface{}, 0, len(rows))
for _, r := range rows {
list = append(list, map[string]interface{}{
"id": r.ID,
"tid": r.Tid,
"provider": r.Provider,
"name": derefAuthIdp(r.Name),
"app_id": derefAuthIdp(r.AppID),
"proxy_url": derefAuthIdp(r.ProxyURL),
"scopes": derefAuthIdp(r.Scopes),
"auth_url": derefAuthIdp(r.AuthURL),
"token_url": derefAuthIdp(r.TokenURL),
"userinfo_url": derefAuthIdp(r.UserinfoURL),
"status": r.Status,
"has_secret": r.AppSecret != nil && *r.AppSecret != "",
})
}
c.serveJSON(map[string]interface{}{
"code": 200, "msg": "success",
"data": map[string]interface{}{"list": list, "total": len(list)},
})
}
// Create 新增配置
// POST /platform/authIdp/create
func (c *PlatformAuthIdpController) Create() {
p, ok := c.parsePayload()
if !ok {
return
}
p.Provider = strings.TrimSpace(p.Provider)
if p.Provider == "" {
c.serveJSON(map[string]interface{}{"code": 400, "msg": "provider 不能为空"})
return
}
if exist := models.Orm.QueryTable(new(models.AuthTenantIdp)).
Filter("tid", p.Tid).Filter("provider", p.Provider).Exist(); exist {
c.serveJSON(map[string]interface{}{"code": 400, "msg": "该登录方式已配置,请直接编辑"})
return
}
item := &models.AuthTenantIdp{
Tid: p.Tid,
Provider: p.Provider,
Status: 1,
}
fillAuthIdp(item, &p)
if p.Status != nil {
item.Status = *p.Status
}
id, err := models.Orm.Insert(item)
if err != nil {
c.serveJSON(map[string]interface{}{"code": 500, "msg": "创建失败: " + err.Error()})
return
}
c.serveJSON(map[string]interface{}{"code": 200, "msg": "success", "data": map[string]interface{}{"id": id}})
}
// Edit 编辑配置(app_secret 留空表示不修改)
// POST /platform/authIdp/edit/:id
func (c *PlatformAuthIdpController) Edit() {
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.serveJSON(map[string]interface{}{"code": 400, "msg": "无效ID"})
return
}
p, ok := c.parsePayload()
if !ok {
return
}
var row models.AuthTenantIdp
if err := models.Orm.QueryTable(new(models.AuthTenantIdp)).Filter("id", id).One(&row); err != nil {
c.serveJSON(map[string]interface{}{"code": 404, "msg": "记录不存在"})
return
}
fillAuthIdp(&row, &p)
if p.Status != nil {
row.Status = *p.Status
}
if _, err := models.Orm.Update(&row); err != nil {
c.serveJSON(map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()})
return
}
c.serveJSON(map[string]interface{}{"code": 200, "msg": "success"})
}
// Delete 删除配置
// DELETE /platform/authIdp/delete/:id
func (c *PlatformAuthIdpController) Delete() {
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.serveJSON(map[string]interface{}{"code": 400, "msg": "无效ID"})
return
}
if _, err := models.Orm.QueryTable(new(models.AuthTenantIdp)).Filter("id", id).Delete(); err != nil {
c.serveJSON(map[string]interface{}{"code": 500, "msg": "删除失败: " + err.Error()})
return
}
c.serveJSON(map[string]interface{}{"code": 200, "msg": "success"})
}
// CallbackHint 返回各平台需要填写的回调地址(方便复制)
// GET /platform/authIdp/callbackHint?provider=wechat
func (c *PlatformAuthIdpController) CallbackHint() {
provider := strings.TrimSpace(c.GetString("provider"))
base := jwtutil.Issuer()
if base == "" {
scheme := "https"
if c.Ctx.Request.TLS == nil {
scheme = "http"
}
base = fmt.Sprintf("%s://%s/auth", scheme, c.Ctx.Request.Host)
}
c.serveJSON(map[string]interface{}{
"code": 200,
"data": map[string]interface{}{
"callback": fmt.Sprintf("%s/third/%s/callback", base, provider),
},
})
}
// ---------------------------------------------------------------- 工具
// fillAuthIdp 把请求参数写入模型;留空字段表示不修改
func fillAuthIdp(item *models.AuthTenantIdp, p *authIdpPayload) {
if v := strings.TrimSpace(p.Name); v != "" {
item.Name = &v
}
if v := strings.TrimSpace(p.AppID); v != "" {
item.AppID = &v
}
if v := strings.TrimSpace(p.AppSecret); v != "" {
item.AppSecret = &v
}
if v := strings.TrimSpace(p.ProxyURL); v != "" {
item.ProxyURL = &v
}
if v := strings.TrimSpace(p.Scopes); v != "" {
item.Scopes = &v
}
if v := strings.TrimSpace(p.AuthURL); v != "" {
item.AuthURL = &v
}
if v := strings.TrimSpace(p.TokenURL); v != "" {
item.TokenURL = &v
}
if v := strings.TrimSpace(p.UserinfoURL); v != "" {
item.UserinfoURL = &v
}
}
func derefAuthIdp(p *string) string {
if p == nil {
return ""
}
return *p
}